Skip to content

Conversation

@Seojegyeong
Copy link
Contributor

@Seojegyeong Seojegyeong commented Aug 12, 2025

🚨 관련 이슈

#90

✨ 변경사항

  • 🐞 BugFix Something isn't working
  • 💻 CrossBrowsing Browser compatibility
  • 🌏 Deploy Deploy
  • 🎨 Design Markup & styling
  • 📃 Docs Documentation writing and editing (README.md, etc.)
  • ✨ Feature Feature
  • 🔨 Refactor Code refactoring
  • ⚙️ Setting Development environment setup
  • ✅ Test Test related (storybook, jest, etc.)

✏️ 작업 내용

API 연결

[공지사항 조회 관련 API]

  • 공지사항 전체 조회
  • 공지사항 상세 조회
  • 공지사항 검색

[자주 묻는 질문 조회 관련 API]

  • 자주 묻는 질문 전체 조회
  • 자주 묻는 질문 검색어 전체 조회

[사용자 API]

  • 비밀번호 변경 API
  • 사용자 정보 변경 API - 닉네임 수정
  • 회원 탈퇴하기 API
  • 설정 : 사용자 기존 닉네임, 이메일 표시

[사용자 API]

  • 사용자 등급 관련 API

[알림 API]

  • 알림 설정 조회
  • 알림 설정 업데이트

[데이트 취향 테스트 API]

  • 취향 데이터 초기화 API

[기타]

  • 미제공 기능 alert로 안내창 띄우기
  • HomePage info 공지사항 맞춰서 수정

😅 미완성 작업

[미완성 작업]
결제 내역 확인, 다음 결제 예정일, 자동 결제 토글 스위치 등
-> 결제 관련 API 없어서 미연결 및 하드코딩 했습니다.

📢 논의 사항 및 참고 사항

@Seojegyeong Seojegyeong linked an issue Aug 12, 2025 that may be closed by this pull request
3 tasks
@coderabbitai
Copy link

coderabbitai bot commented Aug 12, 2025

Summary by CodeRabbit

  • New Features
    • 계정 관리: 비밀번호 변경, 닉네임 변경, 회원 정보/등급 조회
    • 알림 설정: 조회/수정 지원 및 즉시 반영
    • 공지/FAQ: 카테고리별 목록·검색 훅 추가, 상세 조회, 페이지네이션 대응
    • 취향 데이터 초기화 기능 추가
    • 날짜 포맷 유틸과 약관/개인정보처리방침 링크 추가
    • 결제 내역 페이지네이션 도입
    • 설정 모달에 실제 로그아웃 버튼 적용
  • Refactor
    • 공지/FAQ/홈/정보/비밀번호 UI를 API 기반으로 전환, 입력/포커스 UX 개선
  • Chores
    • 일부 경로 접근 보호(ProtectedRoute) 및 라우트 파라미터/내비게이션 정리
  • Removal
    • 설정 진입 페이지 제거, 디바이스 토큰 훅 비활성화, 구 공지 훅 삭제

Walkthrough

계정·FAQ·공지·알람 관련 API·타입·훅을 추가·개편하고 여러 UI를 서버 연동으로 마이그레이션했습니다. FCM 디바이스 토큰 훅은 비활성화되었고 라우트·페이지 정리, 날짜 유틸 및 약관 URL 상수가 추가되었습니다.

Changes

Cohort / File(s) Summary
Account API & Hooks & Types
src/api/auth/account.ts, src/hooks/auth/useAccount.ts, src/types/auth/account.ts
계정 관련 API(비밀번호/닉네임 변경, 회원정보/등급 조회, 탈퇴)와 타입 추가. useAccount 훅으로 쿼리·뮤테이션 래핑(QUERY_KEYS 포함).
Account UI 통합
src/components/common/PasswordEdit.tsx, src/components/settingTab/InfoSetting.tsx, src/pages/setting/DeleteConfirmPage.tsx
비밀번호 변경·닉네임 편집·회원탈퇴 UI가 서버 뮤테이션/쿼리와 연동되도록 변경(로딩·에러·검증 처리 포함).
Alarm 설정 API/Hook/UI
src/api/settingAlarm/alarm.ts, src/hooks/settingAlarm/useAlarms.ts, src/components/settingTab/AlarmSetting.tsx
알람 설정 조회/패치 API·훅 추가. 설정 탭을 서버 동기화(낙관적 업데이트 및 롤백)로 리팩터링.
FAQ: API / Keys / Hooks / Types / 컴포넌트 / 페이지
src/api/faq/faq.ts, src/api/faq/faq.keys.ts, src/hooks/faq/useFaq.ts, src/types/faq/faq.ts, src/pages/question/Question.tsx, src/components/faq/FAQItem.tsx
FAQ 목록·검색 API/키/타입/훅 추가. Question 페이지와 FAQItem을 서버 기반으로 전환 및 접근성·데이터 구조 업데이트.
Notice: API / Keys / Hooks / 페이지
src/api/notice/notice.ts, src/api/notice/notice.keys.ts, src/hooks/notice/useNotice.ts, src/pages/notice/Notice.tsx, src/pages/notice/NoticeDetail.tsx
공지 API 리네이밍·파라미터 정리 및 검색 API 추가, noticeKeys 도입, useNotice 훅 추가. 리스트/상세 페이지 훅 기반으로 마이그레이션 및 날짜 포맷 적용.
Dates Preferences 초기화
src/api/dates/preferences.ts, src/types/dates/preferences.ts, src/components/settingTab/InfoSetting.tsx
취향 데이터 초기화 DELETE API 및 타입 추가, InfoSetting에서 호출 통합.
라우팅·페이지 정리
src/routes/routes.tsx, src/pages/setting/SettingEntryPage.tsx
/setting 진입 페이지 삭제(파일/라우트 제거), 뒤로가기 대상 일부를 /home으로 변경, NoticeDetail 경로 파라미터명 정리, ProtectedRoute 적용 확장.
Device Token 비활성화 및 사용 제거
src/hooks/alarm/useDeviceToken.ts, src/pages/home/HomePage.tsx
useDeviceToken 훅 전체 구현을 주석 처리해 비활성화하고 HomePage 호출을 제거(주석).
공통 유틸/상수/쿼리 제네릭/컴포넌트 개선
src/utils/date.ts, src/constants/policies.ts, src/hooks/customQuery.ts, src/components/common/EditableInputBox.tsx, src/components/settingTab/MembershipSetting.tsx
formatDateDot 유틸 추가, TERMS/PRIVACY URL 상수 추가, useCoreMutation options 제네릭화, EditableInputBox에 readOnly/onEditStart/onFocus 추가, 멤버십 탭 등급 서버 연동.
Notice 타입 변경 및 훅 정리
src/types/notice/notice.ts, src/hooks/notices/useGetNotices.ts
공지 응답 페이징 필드 확장, 불필요 타입 제거, 기존 useGetNotices 훅 삭제(대체 훅 useNotice로 이동).
기타 UI·사소 변경
src/components/common/ToggleSwitch.tsx, src/components/modal/SettingModal.tsx, src/pages/setting/PaymentHistory.tsx, src/pages/setting/DeleteReasonPage.tsx, src/pages/home/HomePage.tsx
토글 OFF 라벨 위치 조정, 설정 모달에 로그아웃 연동, 결제내역 페이지 페이징 추가 및 뒤로가기 경로 변경 등 UI 조정.

Sequence Diagram(s)

sequenceDiagram
  participant UI as PasswordEdit
  participant Hook as useAccount().useChangePassword
  participant API as changePassword
  participant Server

  UI->>Hook: mutate({ currentPassword, newPassword })
  Hook->>API: changePassword(payload)
  API->>Server: PATCH /api/v1/members/passwords { nowPassword, newPassword }
  Server-->>API: 응답(성공/오류)
  API-->>Hook: resolve/reject
  Hook-->>UI: onSuccess/onError 콜백
Loading
sequenceDiagram
  participant UI as AlarmSetting
  participant Hook as usePatchAlarmSettings
  participant API as patchAlarmSettings
  participant Server
  participant Cache as QueryClient

  UI->>UI: 로컬 상태 즉시 변경 (낙관적 업데이트)
  UI->>Hook: mutate(updatedSettings)
  Hook->>API: PATCH /api/v1/alarms/settings
  API->>Server: 업데이트 요청
  Server-->>API: 응답(업데이트된 값)
  Hook->>Cache: invalidate ['alarmSettings']
  Note over UI: 실패 시 로컬 상태 롤백
Loading
sequenceDiagram
  participant UI as NoticePage
  participant Hooks as useNotice()
  participant API as getNotices/searchNotices
  participant Server

  UI->>Hooks: useGetNotices(params) / useSearchNotices(params)
  Hooks->>API: GET /api/v1/notices[(/search)]?...
  API->>Server: 쿼리 전달
  Server-->>API: 목록 데이터 반환
  API-->>Hooks: data
  Hooks-->>UI: 데이터 렌더링
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • yujin5959
  • yeonjin719
  • SEBINorSEBOUT

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 32

🔭 Outside diff range comments (2)
src/firebase/firebase.ts (1)

1-42: Firebase 관련 코드 완전 비활성화 및 의존성 정리 필요
현재 src/firebase/firebase.ts의 Firebase 초기화, 토큰 생성, 서비스워커 등록 코드가 모두 주석 처리되어 실제로 사용되지 않습니다. 그러나 package.json에는 여전히 "firebase": "^12.0.0" 의존성이 남아 있고, 환경 변수(VITE_FIREBASE_API_KEY 등) 설정도 제거되지 않았습니다. 의도된 중단이라면 관련 의존성과 설정을 함께 정리해주세요.

• package.json에서 "firebase" 의존성 제거
.env 및 Vite 설정 파일에서 VITE_FIREBASE_*, VITE_PROJECT_ID 등 Firebase 관련 환경 변수 삭제
• src/firebase/firebase.ts 또는 전체 코드베이스에서 Firebase 참조 파일·주석 제거 또는 파일 삭제

src/hooks/alarm/useDeviceToken.ts (1)

1-40: useDeviceToken 훅이 완전히 비활성화되었습니다.

모든 구현 코드가 주석 처리되어 FCM 토큰 생성 및 서버 전송 기능이 비활성화되었습니다. 이는 디바이스 토큰 기반 푸시 알림 시스템의 완전한 중단을 의미합니다.

Firebase 기능 비활성화가 임시적인 조치라면, 나중에 쉽게 복구할 수 있도록 별도의 브랜치나 백업을 고려해보세요. 또는 기능을 완전히 제거할 계획이라면 관련 파일들과 의존성을 정리하는 것을 도와드릴 수 있습니다.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6fdc4c2 and 761a53f.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock, !**/*.lock
📒 Files selected for processing (26)
  • src/api/auth/account.ts (1 hunks)
  • src/api/faq/faq.keys.ts (1 hunks)
  • src/api/faq/faq.ts (1 hunks)
  • src/api/notice/notice.keys.ts (1 hunks)
  • src/api/notice/notice.ts (1 hunks)
  • src/api/settingAlarm/alarm.ts (1 hunks)
  • src/components/common/EditableInputBox.tsx (1 hunks)
  • src/components/common/PasswordEdit.tsx (6 hunks)
  • src/components/faq/FAQItem.tsx (2 hunks)
  • src/components/settingTab/AlarmSetting.tsx (1 hunks)
  • src/components/settingTab/InfoSetting.tsx (1 hunks)
  • src/firebase/firebase.ts (1 hunks)
  • src/hooks/alarm/useDeviceToken.ts (1 hunks)
  • src/hooks/auth/useAccount.ts (1 hunks)
  • src/hooks/auth/useEmail.ts (1 hunks)
  • src/hooks/faq/useFaq.ts (1 hunks)
  • src/hooks/notice/useNotice.ts (1 hunks)
  • src/hooks/settingAlarm/useAlarms.ts (1 hunks)
  • src/pages/home/HomePage.tsx (2 hunks)
  • src/pages/notice/Notice.tsx (1 hunks)
  • src/pages/notice/NoticeDetail.tsx (1 hunks)
  • src/pages/question/Question.tsx (3 hunks)
  • src/types/auth/account.ts (1 hunks)
  • src/types/faq/faq.ts (1 hunks)
  • src/types/notice/notice.ts (1 hunks)
  • src/types/settingAlarm/alarm.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (20)
src/api/settingAlarm/alarm.ts (2)
src/types/settingAlarm/alarm.ts (3)
  • TGetAlarmSettingsResp (14-14)
  • TAlarmSettings (1-5)
  • TPatchAlarmSettingsResp (15-15)
src/api/axiosInstance.ts (1)
  • axiosInstance (11-14)
src/api/faq/faq.ts (2)
src/types/faq/faq.ts (2)
  • TFaqCategory (17-17)
  • TFetchFaqsResponse (9-15)
src/api/axiosInstance.ts (1)
  • axiosInstance (11-14)
src/api/auth/account.ts (2)
src/types/auth/account.ts (3)
  • TChangePasswordPayload (6-9)
  • TChangeNicknamePayload (12-12)
  • TChangeNicknameResponse (13-18)
src/api/axiosInstance.ts (1)
  • axiosInstance (11-14)
src/hooks/auth/useAccount.ts (3)
src/types/auth/account.ts (8)
  • TChangePasswordMutationOptions (21-21)
  • TChangePasswordMutationResult (22-22)
  • TChangePasswordResponse (10-10)
  • TChangePasswordPayload (6-9)
  • TChangeNicknameMutationOptions (25-25)
  • TChangeNicknameMutationResult (26-26)
  • TChangeNicknameResponse (13-18)
  • TChangeNicknamePayload (12-12)
src/hooks/customQuery.ts (1)
  • useCoreMutation (20-28)
src/api/auth/account.ts (2)
  • changePassword (6-9)
  • changeNickname (12-15)
src/api/faq/faq.keys.ts (1)
src/queryKey/queryKey.ts (1)
  • keyword (4-4)
src/hooks/faq/useFaq.ts (5)
src/types/faq/faq.ts (2)
  • TFaqCategory (17-17)
  • TFetchFaqsResponse (9-15)
src/hooks/customQuery.ts (2)
  • useCoreQuery (7-18)
  • useCoreMutation (20-28)
src/api/faq/faq.keys.ts (1)
  • faqKeys (1-5)
src/api/faq/faq.ts (2)
  • getFaqs (6-15)
  • searchFaqs (18-28)
src/hooks/course/useSearchRegion.tsx (1)
  • useSearchRegion (6-11)
src/api/notice/notice.keys.ts (1)
src/hooks/customQuery.ts (1)
  • useCoreQuery (7-18)
src/types/faq/faq.ts (1)
src/types/common/common.ts (1)
  • TCommonResponse (4-9)
src/types/auth/account.ts (4)
src/types/common/common.ts (1)
  • TUseMutationCustomOptions (17-20)
src/pages/auth/FindPw.tsx (3)
  • data (109-116)
  • err (117-119)
  • FindPw (17-207)
src/hooks/auth/useAuth.ts (1)
  • useAuth (5-14)
src/pages/auth/UserSetting.tsx (1)
  • User (23-178)
src/hooks/auth/useEmail.ts (3)
src/store/useAuthStore.ts (3)
  • email (17-17)
  • set (13-21)
  • IAuthState (3-11)
src/pages/setting/DeleteConfirmPage.tsx (1)
  • storedEmail (30-35)
src/pages/auth/JoinPage.tsx (2)
  • setEmail (179-183)
  • Join (19-190)
src/pages/notice/Notice.tsx (1)
src/hooks/notice/useNotice.ts (1)
  • useNotice (24-58)
src/hooks/settingAlarm/useAlarms.ts (3)
src/hooks/customQuery.ts (2)
  • useCoreQuery (7-18)
  • useCoreMutation (20-28)
src/api/settingAlarm/alarm.ts (2)
  • getAlarmSettings (6-9)
  • patchAlarmSettings (12-15)
src/hooks/auth/useAuth.ts (1)
  • useAuth (5-14)
src/pages/notice/NoticeDetail.tsx (2)
src/hooks/notice/useNotice.ts (1)
  • useNotice (24-58)
src/components/home/info.tsx (1)
  • MainInfo (7-30)
src/components/settingTab/AlarmSetting.tsx (1)
src/components/modal/SettingModal.tsx (1)
  • SettingsModal (16-55)
src/api/notice/notice.ts (2)
src/api/axiosInstance.ts (1)
  • axiosInstance (11-14)
src/types/notice/notice.ts (2)
  • TFetchNoticesResponse (11-17)
  • TFetchNoticeDetailResponse (28-28)
src/pages/question/Question.tsx (3)
src/types/faq/faq.ts (2)
  • TFaqCategory (17-17)
  • TFaqItem (3-7)
src/hooks/faq/useFaq.ts (1)
  • useFaq (14-34)
src/components/faq/FAQItem.tsx (1)
  • FAQItem (13-48)
src/components/settingTab/InfoSetting.tsx (4)
src/hooks/auth/useEmail.ts (1)
  • useUserEmail (3-15)
src/hooks/auth/useAccount.ts (1)
  • useAccount (16-28)
src/api/auth/account.ts (1)
  • changeNickname (12-15)
src/pages/TestInputPage.tsx (4)
  • e (39-39)
  • setNickname (40-40)
  • TestInputPage (8-46)
  • console (41-41)
src/hooks/notice/useNotice.ts (3)
src/hooks/customQuery.ts (1)
  • useCoreQuery (7-18)
src/types/notice/notice.ts (2)
  • TFetchNoticesResponse (11-17)
  • TFetchNoticeDetailResponse (28-28)
src/api/notice/notice.ts (3)
  • getNotices (7-16)
  • getNoticeDetail (19-22)
  • searchNotices (25-37)
src/components/faq/FAQItem.tsx (1)
src/components/common/ToggleSwitch.tsx (2)
  • ToggleSwitch (13-63)
  • IToggleSwitchProps (5-11)
src/components/common/PasswordEdit.tsx (3)
src/hooks/auth/useAccount.ts (1)
  • useAccount (16-28)
src/pages/auth/FindPw.tsx (4)
  • FindPw (17-207)
  • data (109-116)
  • err (117-119)
  • isValid (101-123)
src/hooks/auth/useAuth.ts (1)
  • useAuth (5-14)
🪛 GitHub Check: Build
src/hooks/auth/useAccount.ts

[failure] 24-24:
Argument of type 'TChangeNicknameMutationOptions | undefined' is not assignable to parameter of type 'TUseMutationCustomOptions | undefined'.


[failure] 19-19:
Argument of type 'TChangePasswordMutationOptions | undefined' is not assignable to parameter of type 'TUseMutationCustomOptions | undefined'.

src/hooks/settingAlarm/useAlarms.ts

[failure] 3-3:
'"@/types/settingAlarm/alarm"' has no exported member named 'PatchAlarmSettingsResp'. Did you mean 'TPatchAlarmSettingsResp'?


[failure] 3-3:
'"@/types/settingAlarm/alarm"' has no exported member named 'GetAlarmSettingsResp'. Did you mean 'TGetAlarmSettingsResp'?


[failure] 3-3:
'"@/types/settingAlarm/alarm"' has no exported member named 'AlarmSettings'. Did you mean 'TAlarmSettings'?

🪛 GitHub Actions: CI
src/hooks/auth/useAccount.ts

[error] 19-19: TypeScript error TS2345 during 'tsc -b' step: Argument of type 'TChangePasswordMutationOptions | undefined' is not assignable to parameter of type 'TUseMutationCustomOptions | undefined'.

🪛 Biome (2.1.2)
src/pages/notice/Notice.tsx

[error] 40-40: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 40-40: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 68-79: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

src/pages/question/Question.tsx

[error] 57-57: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 59-59: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 92-104: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

src/components/faq/FAQItem.tsx

[error] 40-40: The elements with this role can be changed to the following elements:

For examples and more information, see WAI-ARIA Roles

(lint/a11y/useSemanticElements)

src/components/common/PasswordEdit.tsx

[error] 82-93: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🔇 Additional comments (32)
src/types/settingAlarm/alarm.ts (3)

1-5: 타입 정의가 명확하고 깔끔합니다.

알람 설정에 필요한 세 가지 타입(이메일, 푸시, SMS)을 boolean으로 명확하게 정의했습니다.


7-12: 제네릭을 활용한 API 응답 래퍼 타입이 우수합니다.

TApiEnvelope<T>는 일관된 API 응답 구조를 보장하며, 다른 API 응답에도 재사용할 수 있어 좋습니다.


14-15: 응답 타입 별칭이 적절합니다.

GET과 PATCH 응답이 동일한 구조를 갖는 것이 합리적이며, 명시적인 타입 별칭으로 가독성을 높였습니다.

src/hooks/settingAlarm/useAlarms.ts (2)

10-15: 조회 훅 구현이 적절합니다.

select를 사용해 응답에서 필요한 데이터만 추출하고, refetchOnWindowFocus를 비활성화한 것이 알람 설정 특성상 합리적입니다.


18-25: 업데이트 훅의 캐시 무효화 로직이 우수합니다.

성공 시 쿼리 캐시를 무효화하여 최신 데이터를 보장하는 로직이 잘 구현되었습니다.

src/api/settingAlarm/alarm.ts (2)

6-9: 조회 API 함수 구현이 적절합니다.

API 경로와 인증 설정이 올바르게 구성되었습니다. withCredentials: true 옵션은 axios 인스턴스에서 이미 설정되어 있어 중복이지만 명시성을 위해 유지하는 것도 좋습니다.


12-15: 업데이트 API 함수 구현이 적절합니다.

PATCH 메서드를 사용하여 부분 업데이트를 수행하고, payload 타입 지정이 명확합니다.

src/components/settingTab/AlarmSetting.tsx (5)

16-18: 서버 데이터 연동이 잘 구현되었습니다.

알람 설정을 서버에서 조회하고 업데이트하는 훅을 적절히 사용했습니다.


24-24: SMS 알람 초기값 변경이 합리적입니다.

서버 연동을 통해 실제 설정값을 받아오므로 초기값을 true로 변경한 것이 적절합니다.


28-35: 서버 데이터와 UI 상태 동기화가 적절합니다.

!! 연산자를 사용하여 서버 값을 boolean으로 확실히 변환하는 로직이 안전합니다.


38-53: 낙관적 업데이트와 롤백 로직이 우수합니다.

UI 상태를 먼저 업데이트하고, 서버 요청이 실패하면 이전 상태로 롤백하는 UX 최적화가 잘 구현되었습니다.


55-59: 아이템 배열 구조가 깔끔합니다.

이전 alarmItemsitems로 단순화하고 키 기반 접근을 위한 구조가 개선되었습니다.

src/components/faq/FAQItem.tsx (3)

5-5: 선택적 카테고리 프로퍼티 변경이 적절합니다.

카테고리를 선택적으로 만든 변경이 FAQ 아이템의 유연성을 높여줍니다.


19-19: 접근성 속성이 올바르게 구현되었습니다.

type="button", aria-expanded, aria-controls 속성이 적절히 설정되어 스크린 리더 사용자에게 좋은 경험을 제공합니다.


21-23: 조건부 렌더링이 안전하게 구현되었습니다.

카테고리가 존재할 때만 배지를 표시하는 로직이 적절합니다.

src/api/faq/faq.keys.ts (1)

1-5: 쿼리 키 구조가 일관성 있게 설계되었습니다.

React Query의 모범 사례에 따라 계층적 키 구조를 잘 구현했습니다. as const 사용으로 타입 안전성도 확보했습니다.

src/hooks/faq/useFaq.ts (3)

15-21: useMemo 의존성 배열이 적절합니다.

파라미터의 개별 속성들을 의존성으로 지정하여 불필요한 재계산을 방지하고 있습니다.


24-24: 키워드 트림 처리가 적절합니다.

검색 시 공백 문자를 자동으로 제거하여 사용자 경험을 개선했습니다.


27-27: 검색 쿼리 활성화 조건이 올바릅니다.

빈 키워드일 때 불필요한 API 호출을 방지하는 로직이 적절합니다.

src/types/faq/faq.ts (1)

17-17: 카테고리 타입이 명확하게 정의되었습니다.

FAQ 카테고리가 명시적인 문자열 리터럴 타입으로 잘 정의되었습니다.

src/api/faq/faq.ts (2)

6-15: API 함수가 적절히 구현되었습니다.

타입 안전성과 파라미터 매핑이 올바르게 처리되었습니다.


18-28: 검색 API 함수가 적절히 구현되었습니다.

선택적 카테고리 파라미터를 포함한 검색 기능이 올바르게 구현되었습니다.

src/hooks/auth/useEmail.ts (1)

6-12: storage 이벤트는 같은 탭에서 발생하지 않아, 동일 탭 내 로컬스토리지 변경 시 상태가 업데이트되지 않습니다.

  • src/hooks/auth/useEmail.tsstorage 이벤트 리스너는 오직 다른 탭에서만 발동합니다.
  • 코드베이스에서 localStorage.setItem('userEmail', …) 호출을 찾을 수 없어, 같은 탭 내에서 userEmail을 갱신하는 로직이 어디에 있는지 불명확합니다.
  • 같은 탭에서 로컬스토리지를 변경할 때도 상태가 바로 반영되도록, 해당 setItem 호출부에서 setEmail을 직접 호출하거나 커스텀 이벤트를 디스패치하는 방안을 고려해 주세요.
src/pages/home/HomePage.tsx (1)

1-1: useDeviceToken 훅 사용이 적절히 비활성화되었습니다.

import와 훅 호출 모두 주석 처리하여 Firebase 토큰 기능 비활성화와 일관성 있게 처리되었습니다.

Also applies to: 13-13

src/api/notice/notice.ts (1)

7-16: 추가 정보 요청: 사용 중인 notice API의 엔드포인트 URL이나 공식 문서(또는 예시 코드) 링크를 공유해 주실 수 있나요?
해당 정보를 확인한 뒤 필터링용 파라미터명이 category인지 noticeCategory인지 정확히 검토하여 알려드리겠습니다.

src/types/auth/account.ts (1)

10-10: changePassword 함수 구현 및 반환 타입 확인 요청
changePassword 함수 정의를 찾지 못했습니다. 해당 함수가 실제로 어떤 응답(예: 성공 시 반환 바디)을 리턴하는지 확인 후, TChangePasswordResponse를 void에서 적절한 타입으로 수정해 주세요.

  • 구현 파일 경로
  • API 호출(response.data) 구조
src/api/auth/account.ts (1)

6-9: 401 에러 처리 로직 불필요

확인 결과 src/api/axiosInstance.ts의 응답 인터셉터에서 이미
error.response?.data.error === 'Unauthorized' 체크 후 토큰 리프레시 및 리다이렉트 로직을 수행하고 있습니다.
따라서 changePassword 함수에 별도의 401 에러 처리 로직을 추가할 필요가 없습니다.

  • 위치: src/api/axiosInstance.ts (response interceptor)

Likely an incorrect or invalid review comment.

src/hooks/notice/useNotice.ts (5)

1-9: 필요한 임포트가 모두 적절히 구성되어 있습니다.

React 훅, React Query, 타입 정의, 커스텀 훅, API 함수, 쿼리 키 등 필요한 모든 의존성이 명확하고 체계적으로 임포트되어 있습니다.


11-22: 타입 정의가 명확하고 일관성 있습니다.

TListParamsTSearchParams 타입이 API 함수의 매개변수와 정확히 일치하며, 선택적 category 필드도 적절히 정의되어 있습니다.


24-33: 공지 목록 훅이 잘 구현되었습니다.

useMemo를 사용한 안정적인 매개변수 메모이제이션과 keepPreviousData를 통한 사용자 경험 개선이 잘 적용되어 있습니다.


42-51: 검색 훅의 구현이 우수합니다.

키워드 트리밍과 빈 문자열 체크를 통한 불필요한 API 호출 방지, 그리고 keepPreviousData를 통한 부드러운 사용자 경험이 잘 구현되어 있습니다.


53-58: 훅들의 반환 구조가 깔끔합니다.

세 개의 훅을 객체로 묶어서 반환하는 방식이 사용하기 편리하고 명확합니다.

@yujin5959
Copy link
Member

로봇에 기가 눌려서 답글 못 달겠어요

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

🔭 Outside diff range comments (9)
src/pages/setting/DeleteReasonPage.tsx.tsx (3)

49-51: 선택한 탈퇴 사유를 전달하고, 미선택 시 ‘다음’ 버튼 비활성화가 필요합니다

선택 상태가 다음 화면으로 전달되지 않아 UX/기능 상 손실이 있습니다. 또한 미선택 상태에서도 이동이 가능해 오동작 우려가 있습니다. state로 넘기고, 미선택 시 비활성화를 권장합니다.

다음과 같이 수정 제안드립니다:

-                        <Link to="/deleteAccount/confirm" className="bg-primary-500 text-white px-20 py-3 rounding-16 font-body1">
-                            다음
-                        </Link>
+                        <Link
+                            to="/deleteAccount/confirm"
+                            state={{ reasons: selected }}
+                            className={`bg-primary-500 text-white px-20 py-3 rounded-16 font-body1 ${selected.length === 0 ? 'pointer-events-none opacity-50' : ''}`}
+                            aria-disabled={selected.length === 0}
+                        >
+                            다음
+                        </Link>
  • 참고: Tailwind 클래스는 보통 rounded-…를 사용합니다. 기존 rounding-16은 오타로 보이며 rounded-16(또는 rounded-[16px])로 변경했습니다.

1-1: 파일 확장자(.tsx.tsx)로 인해 빌드/타입체크 누락 위험이 큽니다

현재 파일명이 ‘DeleteReasonPage.tsx.tsx’로 보입니다. 일반적으로 TSX 파일은 확장자가 .tsx 하나여야 합니다. 번들러/TS가 인식하지 못해 라우팅/빌드 실패로 이어질 수 있습니다. 즉시 파일명을 .tsx로 변경해 주세요.

프로젝트 루트에서 다음과 같이 리네임하는 것을 권장합니다:

# 예시: git으로 파일명 변경
git mv src/pages/setting/DeleteReasonPage.tsx.tsx src/pages/setting/DeleteReasonPage.tsx

5-5: 파일명과 경로 일관성 확인

  • src/components/common/ReansonButton.tsx 파일이 실제로 존재합니다.
  • 따라서 현재 import ReasonButton from '@/components/common/ReansonButton'; 는 올바른 경로입니다.
  • 다만 파일명(ReansonButton.tsx)과 컴포넌트명(ReasonButton)이 불일치해 혼란을 줄 수 있습니다.

제안:

  • 컴포넌트명에 맞춰 파일명도 ReasonButton.tsx 로 변경하세요.
  • 기존 파일을 리네임한 뒤 모든 import 경로를 일괄 수정하시길 권장드립니다.
src/pages/setting/PaymentHistory.tsx (3)

70-72: 리스트 key는 index 대신 고유 식별자를 사용해 주세요

React key로 index를 쓰면 재정렬/삽입 시 의도치 않은 재사용이 발생할 수 있습니다. 주문번호가 고유값이라면 이를 key로 사용하는 편이 안전합니다.

다음과 같이 수정 제안드립니다:

-                            {dummyData.map((item, index) => (
-                                <PaymentRow key={index} data={item} />
+                            {dummyData.map((item) => (
+                                <PaymentRow key={item.orderNumber} data={item} />

90-90: ‘다음 페이지’ 아이콘에 동작을 부여해 주세요

현재 아이콘이 정적입니다. 클릭 시 다음 페이지로 넘어가도록 onClick을 부여하고, 마지막 페이지에서 비활성화/무시 처리를 권장합니다.

다음과 같이 수정 제안드립니다:

-                    <Forward className=" fill-current text-default-gray-500" />
+                    <button
+                        onClick={() => setCurrentPage((p) => Math.min(p + 1, 5))}
+                        aria-label="다음 페이지"
+                        className="disabled:opacity-50"
+                        disabled={currentPage === 5}
+                    >
+                        <Forward className="fill-current text-default-gray-500" />
+                    </button>

11-33: 더미 데이터 중 주문번호가 중복됩니다(키 고유성/표시 신뢰성 영향)

‘202219487’가 2회 등장합니다. 실제 연동 전이라도 더미 데이터의 식별자 중복은 key/테스트/UX에 혼선을 줄 수 있으니 고유하도록 정리해 주세요. 또한 실제 연동 시에는 타입(인터페이스) 명시를 권장합니다.

src/components/common/EditableInputBox.tsx (2)

91-98: 닉네임 readOnly일 때 ‘수정’ 버튼 노출/동작 차단 필요

현재 readOnly=true인 경우에도 (닉네임 모드에서) ‘수정’ 버튼이 노출되어 편집 상태로 진입할 수 있습니다. 읽기 전용을 확실히 보장하려면 readOnly일 때 버튼을 숨기거나 비활성화해 주세요.

예시:

  • 버튼 노출 조건에 readOnly 배제
  • onClick 보호 로직 추가
{isNickname && !isEditing && !readOnly && (
  <button
    /* type="button"도 함께 권장 */
    className="absolute right-3 top-1/2 -translate-y-1/2 font-body1 px-3 py-1 rounded-full bg-default-gray-400 text-default-gray-700"
    onClick={() => setIsEditing(true)}
  >
    수정
  </button>
)}

91-98: 버튼 type 명시 누락

폼 내에서의 오동작을 방지하려면 ‘수정’ 버튼에 type="button"을 명시해 주세요.

예시:

<button
  type="button"
  onClick={() => setIsEditing(true)}
  className="absolute right-3 top-1/2 -translate-y-1/2 font-body1 px-3 py-1 rounded-full bg-default-gray-400 text-default-gray-700"
>
  수정
</button>
src/types/notice/notice.ts (1)

11-18: 페이지네이션 메타의 기준(0/1-based) 주석을 명시해 주세요

currentPage, totalPages, currentSize의 의미(0/1 기반, 서버 규약)를 주석으로 고정하면 UI·API 간 불일치로 인한 버그를 줄일 수 있습니다. 현재 페이지 전송은 0-based로 보입니다(Notice.tsx에서 currentPage - 1). 타입 주석에 간략히 명시 부탁드립니다.

♻️ Duplicate comments (9)
src/hooks/settingAlarm/useAlarms.ts (1)

4-4: 타입 임포트 네이밍 수정 완료 — 빌드 오류 해결됨

T 접두사 타입으로 정상화되어 과거 정적 분석 오류가 해소되었습니다. 현재 임포트/사용 모두 일관적입니다.

src/api/settingAlarm/alarm.ts (1)

7-8: withCredentials 중복 제거 반영 확인 — 간결하고 좋습니다

인스턴스 기본값을 신뢰하도록 정리되어 중복 옵션이 제거되었습니다. 유지보수성과 가독성이 개선됩니다.

Also applies to: 13-14

src/components/faq/FAQItem.tsx (1)

34-36: 아코디언 애니메이션의 max-height를 동적으로 측정하는 것을 권장합니다.

고정값 max-h-[1000px]는 긴 콘텐츠에서 끊기는 문제가 생깁니다. scrollHeight를 활용해 콘텐츠 높이를 측정해 주세요. (이전 코멘트와 동일 제안)

예시 구현:

// 컴포넌트 상단
const [contentHeight, setContentHeight] = useState(0);
const contentRef = useRef<HTMLDivElement>(null);

useEffect(() => {
  if (contentRef.current) {
    setContentHeight(contentRef.current.scrollHeight);
  }
}, [item.content, isOpen]);

// 섹션 요소
<section
  id={contentId}
  aria-labelledby={`faq-toggle-${item.faqId}`}
  aria-hidden={!isOpen}
  className="ml-0 sm:ml-9 overflow-hidden transition-[max-height,opacity] duration-200 ease-in-out"
  style={{ maxHeight: isOpen ? contentHeight : 0, opacity: isOpen ? 1 : 0 }}
>
  <div ref={contentRef} className="px-4 py-3 rounded-md leading-relaxed whitespace-pre-line text-default-gray-700 font-body2 bg-white/50">
    {item.content}
  </div>
</section>
src/pages/question/Question.tsx (1)

82-82: 빈 onSearchClick 콜백 정리 또는 의미 있는 동작으로 대체

불필요한 빈 함수는 제거하거나, 페이지/상태 초기화 등 실제 동작을 연결해 주세요. (이전 코멘트와 동일 맥락)

예시:

-                onSearchClick={() => {}}
+                onSearchClick={() => {
+                    setCurrentPage(1);
+                    setOpenedIndex(null);
+                }}
src/components/common/PasswordEdit.tsx (1)

79-86: 취소 버튼에 type 명시 필요

취소 버튼에 type="button"을 명시해 폼 내에서의 의도치 않은 submit을 방지해 주세요.

다음과 같이 수정:

-                    <button
+                    <button
+                        type="button"
                         onClick={handleCancel}
                         className="px-4 py-1.5 rounding-16 bg-default-gray-400 text-default-gray-700 font-body1 disabled:opacity-60"
                         disabled={isPending}
                     >
                         취소
                     </button>
src/pages/notice/Notice.tsx (2)

30-35: 훅 규칙 위반 수정 확인 — 조건부 호출 제거 Good

두 쿼리를 항상 호출하고 enabled로 토글하는 패턴으로 수정되어 안전합니다.


60-71: 카테고리 버튼 type 명시 반영 확인

type="button"이 추가되어 폼 제출 부작용이 방지되었습니다.

src/hooks/notice/useNotice.ts (2)

27-34: params 안정화(useMemo) 처리 깔끔합니다

이전 피드백대로 새로운 객체를 구성해 참조 안정성을 보장한 점 좋습니다.


45-45: 공지 상세 enabled 조건을 정수로 한정

음수/실수 방지를 위해 Number.isFinite 대신 정수 체크를 권장합니다.

-            enabled: Number.isFinite(id) && id > 0,
+            enabled: Number.isInteger(id) && id > 0,
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 761a53f and f8aebe8.

📒 Files selected for processing (19)
  • src/api/auth/account.ts (1 hunks)
  • src/api/notice/notice.keys.ts (1 hunks)
  • src/api/notice/notice.ts (1 hunks)
  • src/api/settingAlarm/alarm.ts (1 hunks)
  • src/components/common/EditableInputBox.tsx (3 hunks)
  • src/components/common/PasswordEdit.tsx (5 hunks)
  • src/components/faq/FAQItem.tsx (1 hunks)
  • src/components/settingTab/InfoSetting.tsx (1 hunks)
  • src/hooks/faq/useFaq.ts (1 hunks)
  • src/hooks/notice/useNotice.ts (1 hunks)
  • src/hooks/settingAlarm/useAlarms.ts (1 hunks)
  • src/pages/notice/Notice.tsx (1 hunks)
  • src/pages/notice/NoticeDetail.tsx (1 hunks)
  • src/pages/question/Question.tsx (3 hunks)
  • src/pages/setting/DeleteReasonPage.tsx.tsx (1 hunks)
  • src/pages/setting/PaymentHistory.tsx (1 hunks)
  • src/pages/setting/SettingEntryPage.tsx (0 hunks)
  • src/routes/routes.tsx (0 hunks)
  • src/types/notice/notice.ts (2 hunks)
💤 Files with no reviewable changes (2)
  • src/pages/setting/SettingEntryPage.tsx
  • src/routes/routes.tsx
🧰 Additional context used
🧬 Code Graph Analysis (14)
src/pages/setting/PaymentHistory.tsx (1)
src/components/settingTab/MembershipSetting.tsx (3)
  • label (22-28)
  • MembershipSetting (6-56)
  • button (48-52)
src/api/auth/account.ts (2)
src/pages/auth/FindPw.tsx (3)
  • data (109-116)
  • err (117-119)
  • FindPw (17-207)
src/api/auth/auth.ts (2)
  • TFindPasswordValues (53-56)
  • TSignupValues (18-21)
src/pages/setting/DeleteReasonPage.tsx.tsx (5)
src/components/settingTab/MembershipSetting.tsx (2)
  • label (22-28)
  • button (48-52)
src/pages/setting/SettingEntryPage.tsx (2)
  • requestedTab (14-22)
  • SettingEntryPage (8-30)
src/pages/dateTest/DatetestResult.tsx (1)
  • navigate (207-207)
src/pages/setting/DeleteConfirmPage.tsx (1)
  • navigate (51-51)
src/components/home/info.tsx (1)
  • navigate (16-16)
src/hooks/settingAlarm/useAlarms.ts (2)
src/hooks/customQuery.ts (2)
  • useCoreMutation (20-28)
  • useCoreQuery (7-18)
src/hooks/auth/useAuth.ts (1)
  • useAuth (5-14)
src/components/settingTab/InfoSetting.tsx (5)
src/hooks/auth/useEmail.ts (1)
  • useUserEmail (3-15)
src/hooks/auth/useAccount.ts (1)
  • useAccount (16-28)
src/api/auth/account.ts (1)
  • changeNickname (12-15)
src/components/common/EditableInputBox.tsx (1)
  • EditableInputBox (21-128)
src/pages/TestInputPage.tsx (2)
  • e (39-39)
  • TestInputPage (8-46)
src/hooks/faq/useFaq.ts (3)
src/types/faq/faq.ts (1)
  • TFaqCategory (17-17)
src/hooks/customQuery.ts (2)
  • useCoreQuery (7-18)
  • useCoreMutation (20-28)
src/hooks/course/useSearchRegion.tsx (1)
  • useSearchRegion (6-11)
src/pages/question/Question.tsx (3)
src/types/faq/faq.ts (2)
  • TFaqCategory (17-17)
  • TFaqItem (3-7)
src/hooks/faq/useFaq.ts (2)
  • useGetFaqs (16-23)
  • useSearchFaqs (26-33)
src/components/faq/FAQItem.tsx (1)
  • FAQItem (14-42)
src/api/notice/notice.keys.ts (1)
src/hooks/customQuery.ts (1)
  • useCoreQuery (7-18)
src/api/settingAlarm/alarm.ts (1)
src/components/settingTab/AlarmSetting.tsx (6)
  • AlarmSetting (15-50)
  • IAlarmSettingState (9-13)
  • div (40-47)
  • type (24-29)
  • prev (25-28)
  • handleToggle (45-45)
src/pages/notice/Notice.tsx (3)
src/hooks/notice/useNotice.ts (1)
  • useNotice (24-65)
src/types/notice/notice.ts (1)
  • TNoticeItem (4-9)
src/components/common/EditableInputBox.tsx (1)
  • EditableInputBox (21-128)
src/hooks/notice/useNotice.ts (2)
src/components/home/info.tsx (1)
  • MainInfo (7-30)
src/hooks/customQuery.ts (1)
  • useCoreMutation (20-28)
src/pages/notice/NoticeDetail.tsx (1)
src/hooks/notice/useNotice.ts (1)
  • useNotice (24-65)
src/components/common/PasswordEdit.tsx (2)
src/hooks/auth/useAccount.ts (1)
  • useAccount (16-28)
src/pages/auth/FindPw.tsx (4)
  • FindPw (17-207)
  • isValid (101-123)
  • err (117-119)
  • data (109-116)
src/components/common/EditableInputBox.tsx (1)
src/pages/TestInputPage.tsx (2)
  • TestInputPage (8-46)
  • e (39-39)
🪛 GitHub Check: Build
src/hooks/notice/useNotice.ts

[failure] 53-53:
Expected 3-4 arguments, but got 1.

🪛 Biome (2.1.2)
src/components/common/PasswordEdit.tsx

[error] 82-93: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

src/components/faq/FAQItem.tsx

[error] 32-32: The elements with this role can be changed to the following elements:

For examples and more information, see WAI-ARIA Roles

(lint/a11y/useSemanticElements)

🔇 Additional comments (23)
src/hooks/settingAlarm/useAlarms.ts (1)

21-25: axiosInstance에서 이미 401 처리 로직이 구현되어 있습니다.

axiosInstance.ts를 확인해 보니

  • withCredentials: true
  • response interceptor에서 error.response?.data.error === 'Unauthorized'일 때 토큰 재발급, 실패 시 logout/리다이렉트 로직이 구현되어 있어
    useCoreMutation의 onError에 별도 401 전용 처리를 추가할 필요가 없습니다.

• src/api/axiosInstance.ts: withCredentials 설정
• src/api/axiosInstance.ts: response interceptor 내 Unauthorized 처리

src/api/settingAlarm/alarm.ts (2)

6-9: 401 처리 인터셉터가 axiosInstance 레벨에서 구현되어 있습니다
src/api/axiosInstance.ts에서 이미 다음이 확인되었습니다:

  • withCredentials: true 설정
  • response 인터셉터에서 error.response?.data.error === 'Unauthorized' 체크 후 토큰 재발급 → 재시도 또는 로그아웃/리다이렉트 로직
    따라서 개별 API에서 401을 처리할 필요 없이, 현재 구성이 적절히 적용되어 있습니다.

12-12: 서버 부분 업데이트 지원 여부 확인 필요

서버가 PATCH 요청에서 부분 업데이트를 허용하는지 확인해 주세요. 허용 시 Partial<TAlarmSettings>를 사용하면 불필요한 필드 전송을 줄여 네트워크 효율을 높일 수 있습니다.

• 대상 파일

  • src/api/settingAlarm/alarm.ts (12번 줄)

• 제안된 변경사항

-export async function patchAlarmSettings(payload: TAlarmSettings): Promise<TPatchAlarmSettingsResp> {
+export async function patchAlarmSettings(payload: Partial<TAlarmSettings>): Promise<TPatchAlarmSettingsResp> {

서버 스펙(API 문서 또는 백엔드 개발팀) 확인 후 반영해 주세요.

src/pages/setting/PaymentHistory.tsx (1)

46-46: 뒤로가기 우선, 홈 폴백 처리 제안

고정 경로(/home)로만 이동할 경우 브라우저 히스토리가 없으면 컨텍스트가 손실될 수 있어, 히스토리가 있으면 뒤로가기, 없으면 홈으로 이동하도록 수정을 권장드립니다.

  • 대상 위치
    • 파일: src/pages/setting/PaymentHistory.tsx
    • 46행

  • 수정 제안 (diff):

- <button onClick={() => navigate('/home')}>
+ <button onClick={() => (window.history.length > 1 ? navigate(-1) : navigate('/home'))}>
  • 추가 확인 사항
    MembershipSetting에서 PaymentHistory로 이동할 때 전달하는 state(예: { openMembership: true })가 실제로 사용되고 있는지
    • 홈 페이지(src/pages/home)에서 해당 state를 수신/처리하고 있는지

위 사항 확인 후, 컨텍스트 유지가 필요하다면 홈으로 이동할 때도

navigate('/home', { state: { openSettingTab: '멤버십' } });

와 같이 상태를 전달해주세요.

src/components/faq/FAQItem.tsx (1)

14-16: ID 생성 방식 적절 — 충돌/접근성 측면에서 안전합니다.

faqId를 활용해 contentId를 생성한 점 좋습니다. 이전의 질문 텍스트 기반 ID 대비 안전성과 일관성이 크게 개선되었습니다.

src/hooks/faq/useFaq.ts (1)

19-23: 쿼리 키/옵션 구성 적절합니다.

  • useMemo로 안정화된 파라미터를 키/쿼리에 일관되게 사용한 점 좋습니다.
  • 검색 훅의 enabled: stable.keyword.length > 0 가드도 의도에 부합합니다.

Also applies to: 28-33

src/pages/question/Question.tsx (4)

51-64: Hook 규칙 준수로 리팩터링된 부분 좋습니다.

조건부 호출을 제거하고 enabled로 토글하는 전략이 올바르며, activeQuery로 결과를 선택하는 흐름도 명확합니다.


90-103: 카테고리 버튼 type 지정 및 상태 반영 로직 깔끔합니다.

type="button" 추가로 폼 제출 부작용을 방지했고, 활성화 스타일도 명확합니다.


114-120: 안정적인 key 사용과 데이터 매핑 적절

faqId를 key로 사용하고, 서버 스키마에 맞춰 FAQItemcategoryLabel을 주입한 점 좋습니다.


31-38: useMemo 의존성 최적화 반영 확인

상수 size를 의존성에서 제외하고, 필요한 값만 포함한 점 적절합니다.

Also applies to: 41-49

src/components/common/EditableInputBox.tsx (1)

18-19: readOnly 지원 로직 반영된 점 좋습니다

  • 인터페이스에 readOnly 추가, 기본값 디폴팅, 실제 input의 readOnly에 반영까지 일관되게 처리되어 있습니다. 닉네임 모드에서 편집 중이 아닐 때 입력을 막는 기존 로직과도 잘 합쳐졌습니다.

Also applies to: 32-33, 82-82

src/api/auth/account.ts (1)

12-15: 닉네임 변경 API 래핑 적절

엔드포인트/반환 타입 제네릭 사용과 data 반환이 일관됩니다. 상위 훅/페이지와의 연계도 적절합니다.

src/components/common/PasswordEdit.tsx (3)

100-106: ‘수정’ 버튼 type 지정 반영👍

type="button" 명시로 a11y/lint 이슈를 예방합니다.


172-179: 저장 버튼 type 지정 및 로딩 상태 처리 적절

  • type="button" 명시 OK
  • isPending에 따른 비활성/라벨 토글도 적절합니다.

89-99: 비편집 모드에서 마스킹/자동완성 제어 적절

MASK 적용과 autoComplete="new-password"로 비밀번호 관리자 개입을 최소화하는 접근이 합리적입니다.

src/components/settingTab/InfoSetting.tsx (3)

25-40: 닉네임 변경 성공/실패 처리 플로우 합리적

  • 성공 시 상태/로컬스토리지 동기화 후 사용자 알림까지 일관적입니다.
  • 401→‘로그인이 필요합니다.’ 메시지 분기 처리도 적절합니다.

79-86: ‘취향 데이터 초기화’ 버튼 개선 반영

type과 onClick 명시로 기본 submit 방지 및 동작 명확화가 되었습니다.


90-99: 외부 정책 링크를 a 태그로 전환한 점 적절

target/_blank + rel="noopener noreferrer" 적용이 보안 측면에서 좋습니다.

Also applies to: 101-109

src/types/notice/notice.ts (1)

11-18: TFetchNoticesResponse 제네릭 재사용 깔끔합니다

TCommonResponse 제네릭을 활용해 목록 응답을 명확히 모델링한 점 좋습니다. API 계층과 훅에서 타입 안정성이 높아집니다.

src/api/notice/notice.ts (1)

6-16: axios 제네릭과 파라미터 매핑이 정확합니다

엔드포인트, 쿼리 파라미터(neoticeCategory/page/size), 응답 제네릭 지정이 명확하며, 반환값 전파도 적절합니다.

src/hooks/notice/useNotice.ts (3)

51-55: 검색어 trim + enabled 조건 적절합니다

공백 검색 방지 및 이전 데이터 유지 전략이 적절합니다.


6-6: useCoreQuery import 경로 확인 완료

useCoreQuerysrc/hooks/customQuery.ts에서 직접 export되고 있어, 현재 @/hooks/customQuery로의 import 경로가 정확합니다. 추가 수정은 필요 없습니다.


53-57: noticeKeys.search 인자 시그니처 확인 요청
src/hooks/notice/useNotice.ts에서 noticeKeys.search(stable)로 객체 하나를 넘기고 있는데, src/api/notice/notice.keys.ts에 정의된 search가 개별 인자(예: keyword, category, page, size 등)를 요구할 수 있습니다. 실제 시그니처를 확인 후 아래 예시처럼 수정해 주세요.

– 확인 위치:
• src/api/notice/notice.keys.ts 내 export const noticeKeys 객체의 search 정의

– 수정 예시:

- return useCoreQuery<TFetchNoticesResponse>(noticeKeys.search(stable), () => searchNotices(stable), {
+ return useCoreQuery<TFetchNoticesResponse>(
+     noticeKeys.search(
+         stable.keyword,
+         stable.category,
+         stable.page,
+         stable.size
+     ),
+     () => searchNotices(stable),
+     {
          enabled: stable.keyword.length > 0,
          placeholderData: keepPreviousData,
          ...options,
- });
+     }
+ );

실제 정의된 인자 순서와 타입에 맞춰 전달해 주세요.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (9)
src/pages/notice/NoticeDetail.tsx (2)

9-11: 유효하지 않은 ID 처리 순서 개선 필요

ID 유효성 검사를 훅 호출 이전에 수행하면 불필요한 API 호출을 방지할 수 있습니다.


16-16: 주석이 실제 흐름과 반대로 적혀 있습니다

지금은 훅 호출 이후에 유효성 검사를 통해 렌더링을 가드합니다. 혼동을 피하려면 주석을 "Id 유효성 검사 -> 렌더링 가드" 등으로 정정해 주세요.

src/api/auth/account.ts (3)

8-9: withCredentials 중복 설정 제거 권장

axiosInstance에서 이미 withCredentials: true를 기본값으로 설정하고 있으므로, 개별 요청 옵션에서 중복으로 명시할 필요가 없습니다.


13-16: 닉네임 변경 API 401 에러 처리 필요

PR 설명에서 닉네임 변경 API도 401 에러를 반환한다고 명시되어 있습니다. 비밀번호 변경과 동일하게 인증 에러 처리가 필요합니다.


7-10: changePassword 반환 타입과 훅 제네릭 불일치

changePassword가 Promise<void>를 반환하지만, 훅에서는 TChangePasswordResponse로 사용 중인 것으로 보입니다. 훅의 제네릭을 void로 맞춰야 타입 안전성을 확보할 수 있습니다.

src/hooks/notice/useNotice.ts (3)

53-62: TypeScript 제네릭 타입 오류 수정 필요

동일한 TypeScript 제네릭 타입 오류가 있습니다.

-    const useSearchNotices = (params: TSearchParams, options?: Parameters<typeof useCoreQuery<TFetchNoticesResponse>>[2]) => {
+    const useSearchNotices = (params: TSearchParams, options?: Parameters<typeof useCoreQuery>[2]) => {

28-43: TypeScript 제네릭 타입 오류 수정 필요

Parameters<typeof useCoreQuery<TFetchNoticesResponse>>[2] 형태는 TypeScript에서 허용되지 않습니다. typeof에는 제네릭을 적용할 수 없으므로 수정이 필요합니다.

-    const useGetNotices = (params: TListParams, options?: Parameters<typeof useCoreQuery<TFetchNoticesResponse>>[2]) => {
+    const useGetNotices = (params: TListParams, options?: Parameters<typeof useCoreQuery>[2]) => {

46-50: TypeScript 제네릭 타입 오류 수정 필요

동일한 TypeScript 제네릭 타입 오류가 있습니다.

-    const useGetNoticeDetail = (id: number, options?: Parameters<typeof useCoreQuery<TFetchNoticeDetailResponse>>[2]) =>
+    const useGetNoticeDetail = (id: number, options?: Parameters<typeof useCoreQuery>[2]) =>
src/hooks/auth/useAccount.ts (1)

18-40: TypeScript 타입 오류로 인한 빌드 실패가 해결되었습니다

이전 커밋에서 지적된 useCoreMutation 타입 불일치 문제가 src/hooks/customQuery.ts의 제네릭 타입 개선으로 해결되어, 이제 적절한 타입 매개변수가 자동으로 전달됩니다.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f8aebe8 and 258b3a4.

📒 Files selected for processing (12)
  • src/api/auth/account.ts (1 hunks)
  • src/api/notice/notice.keys.ts (1 hunks)
  • src/hooks/auth/useAccount.ts (1 hunks)
  • src/hooks/auth/useEmail.ts (1 hunks)
  • src/hooks/customQuery.ts (1 hunks)
  • src/hooks/notice/useNotice.ts (1 hunks)
  • src/pages/notice/Notice.tsx (1 hunks)
  • src/pages/notice/NoticeDetail.tsx (1 hunks)
  • src/pages/setting/DeleteConfirmPage.tsx (4 hunks)
  • src/routes/routes.tsx (1 hunks)
  • src/types/auth/account.ts (1 hunks)
  • src/utils/date.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (11)
src/utils/date.ts (1)
src/utils/formatDateInput.tsx (1)
  • formatDateInput (1-11)
src/hooks/customQuery.ts (2)
src/types/common/common.ts (1)
  • TUseMutationCustomOptions (17-20)
src/hooks/auth/useAuth.ts (1)
  • useAuth (5-14)
src/api/auth/account.ts (3)
src/types/auth/account.ts (4)
  • TChangePasswordPayload (6-9)
  • TChangeNicknamePayload (12-12)
  • TChangeNicknameResponse (13-18)
  • TMemberInfo (29-40)
src/api/axiosInstance.ts (1)
  • axiosInstance (11-14)
src/types/common/common.ts (1)
  • TCommonResponse (4-9)
src/routes/routes.tsx (3)
src/pages/setting/SettingEntryPage.tsx (1)
  • SettingEntryPage (8-30)
src/pages/setting/DeleteReasonPage.tsx.tsx (1)
  • navigate (32-32)
src/components/home/info.tsx (1)
  • MainInfo (7-30)
src/pages/setting/DeleteConfirmPage.tsx (2)
src/hooks/auth/useAccount.ts (1)
  • useAccount (18-40)
src/components/settingTab/InfoSetting.tsx (1)
  • InfoSetting (9-67)
src/pages/notice/Notice.tsx (5)
src/hooks/notice/useNotice.ts (1)
  • useNotice (26-69)
src/types/notice/notice.ts (1)
  • TNoticeItem (4-9)
src/components/common/EditableInputBox.tsx (1)
  • EditableInputBox (21-128)
src/utils/date.ts (1)
  • formatDateDot (1-8)
src/components/common/navigator.tsx (1)
  • Navigator (10-43)
src/types/auth/account.ts (3)
src/types/common/common.ts (1)
  • TUseMutationCustomOptions (17-20)
src/hooks/auth/useAuth.ts (1)
  • useAuth (5-14)
src/pages/auth/FindPw.tsx (2)
  • data (109-116)
  • err (117-119)
src/hooks/auth/useEmail.ts (1)
src/components/settingTab/InfoSetting.tsx (2)
  • storedEmail (13-19)
  • InfoSetting (9-67)
src/hooks/notice/useNotice.ts (4)
src/hooks/customQuery.ts (1)
  • useCoreQuery (7-18)
src/types/notice/notice.ts (2)
  • TFetchNoticesResponse (12-18)
  • TFetchNoticeDetailResponse (30-30)
src/api/notice/notice.keys.ts (1)
  • noticeKeys (1-12)
src/api/notice/notice.ts (3)
  • getNotices (7-16)
  • getNoticeDetail (19-22)
  • searchNotices (25-37)
src/pages/notice/NoticeDetail.tsx (2)
src/hooks/notice/useNotice.ts (1)
  • useNotice (26-69)
src/utils/date.ts (1)
  • formatDateDot (1-8)
src/hooks/auth/useAccount.ts (4)
src/types/auth/account.ts (8)
  • TChangePasswordMutationOptions (21-21)
  • TChangePasswordMutationResult (22-22)
  • TChangePasswordResponse (10-10)
  • TChangePasswordPayload (6-9)
  • TChangeNicknameMutationOptions (25-25)
  • TChangeNicknameMutationResult (26-26)
  • TChangeNicknameResponse (13-18)
  • TChangeNicknamePayload (12-12)
src/hooks/customQuery.ts (2)
  • useCoreMutation (21-29)
  • useCoreQuery (7-18)
src/api/auth/account.ts (4)
  • changePassword (7-10)
  • changeNickname (13-16)
  • deleteMember (19-21)
  • getMemberInfo (23-26)
src/types/common/common.ts (1)
  • TUseMutationCustomOptions (17-20)
🔇 Additional comments (15)
src/hooks/auth/useEmail.ts (1)

3-21: 훅 구현이 잘 되어 있습니다!

localStorage 접근 시 에러 처리가 적절히 구현되었고, storage 이벤트를 통한 크로스 탭 동기화도 올바르게 처리되었습니다.

src/hooks/customQuery.ts (1)

20-21: 타입 안전성 개선이 잘 되었습니다!

제네릭 타입 매개변수를 추가하여 mutation 옵션의 타입 안전성이 향상되었습니다.

src/pages/setting/DeleteConfirmPage.tsx (1)

34-35: 옵셔널 체이닝 사용 개선 필요

memberData?.result?.email ?? '' 대신 더 명확한 기본값 처리를 고려해 보세요.

-    const userEmail = memberData?.result?.email ?? ''; // ← 여기서 이메일 사용
+    const userEmail = memberData?.result?.email || '';

Likely an incorrect or invalid review comment.

src/pages/notice/NoticeDetail.tsx (1)

35-35: 날짜 포맷 일관화 제안

리스트/상세 모두 동일한 로케일과 포맷을 쓰면 UX가 좋아집니다. formatDateDot 유틸이 이미 일관된 포맷을 제공하고 있어 잘 적용되었습니다.

src/api/notice/notice.keys.ts (2)

11-11: noticeKeys.search 호출 시 인자 불일치 문제

noticeKeys.search는 객체 파라미터를 받도록 정의되어 있으나, useNotice.ts에서 noticeKeys.search(stable) 형태로 올바르게 호출하고 있습니다. 현재 구현이 정상적으로 작동합니다.


5-5: React Query 캐시 키 안정성 확보

객체 파라미터를 캐시 키에 직접 포함시키면 참조 변경으로 인한 캐시 무효화가 발생할 수 있습니다. 하지만 useNotice.ts에서 useMemo로 안정화된 객체를 전달하므로 현재 구현은 안전합니다.

src/pages/notice/Notice.tsx (3)

33-36: React Hook 규칙 준수로 개선

조건부 훅 호출을 피하고 두 훅을 모두 호출한 후 결과를 선택하는 방식으로 올바르게 구현되었습니다. enabled 옵션으로 적절히 제어하고 있어 React Hook 규칙을 잘 준수하고 있습니다.


63-63: 카테고리 버튼 타입 명시 완료

버튼에 type="button" 속성이 올바르게 추가되어 있습니다.


88-88: 목록의 날짜 표시 포맷 통일 완료

formatDateDot 유틸을 사용하여 상세 페이지와 동일한 날짜 포맷으로 통일되었습니다. 일관된 UX를 제공합니다.

src/hooks/notice/useNotice.ts (2)

30-37: 매개변수 안정화 잘 구현됨

useMemo를 사용하여 개별 값들을 의존성 배열에 포함시켜 객체 참조 변경으로 인한 예기치 않은 재렌더링을 방지하고 있습니다.


48-48: ID 유효성 검사 조건 개선 제안

Number.isFinite(id) && id > 0 조건이 적절합니다. 양의 유한수일 때만 쿼리가 활성화되도록 잘 구현되어 있습니다.

src/hooks/auth/useAccount.ts (1)

18-40: 계정 관련 훅들을 깔끔하게 그룹화했습니다

useAccount 함수를 통해 비밀번호 변경, 닉네임 변경, 회원 탈퇴, 사용자 정보 조회 기능을 논리적으로 그룹화하여 코드 구성이 명확해졌습니다.

src/types/auth/account.ts (3)

1-40: 타입 정의가 체계적으로 잘 구성되었습니다

API 요청/응답 타입부터 React Query 관련 타입까지 계정 기능에 필요한 모든 타입이 명확하게 정의되었습니다. 특히 mutation 옵션과 결과 타입의 분리가 적절합니다.


13-18: 일관된 응답 구조 확인 완료
TChangeNicknameResponse가 프로젝트 내 다른 API 응답 타입(src/types/common/common.ts의 공통 Response 구조)과 동일한
isSuccess, code, message, result 필드를 따르고 있어 추가 조치는 필요 없습니다.


29-40: TMemberInfo 사용 위치 및 UI 노출 여부 추가 확인 필요

TMemberInfo 타입(email, 전화번호, 생년월일, 성별 등 민감 정보)을 getMemberInfo API 호출에서 사용하고 있음이 확인되었습니다. 현재 코드상에서는 src/api/auth/account.ts의 API 레이어에서만 참조되고 있지만, 이 응답 데이터를 실제로 렌더링하거나 저장하는 클라이언트 측 컴포넌트·상태 관리 로직을 추가로 검토해 주세요. 민감 정보가 화면에 그대로 노출되거나 불필요하게 저장·로깅되지 않도록 보호 조치를 확인해야 합니다.

  • src/api/auth/account.ts: getMemberInfo()
  • 응답값을 소비하는 컴포넌트(프로필, 사이드바 등 UI 요소) 및 상태 관리 훅 점검 필요
  • 화면에 표시되는 필드 중 마스킹이 필요한 항목이 있는지 검토

@yeonjin719
Copy link
Member

충돌 한 번 해결해주세요! ㅠ

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

♻️ Duplicate comments (9)
src/components/settingTab/InfoSetting.tsx (3)

56-61: 닉네임 제출 로직의 trim/중복 방지/중복요청 방어가 잘 반영됨

공백 입력 차단, 동일 값 변경 방지, pending 상태 방지 모두 적절합니다. 이전 제안사항이 반영되었습니다.


91-99: 취향 데이터 초기화 버튼: 타입/핸들러 추가 반영 — 좋습니다

type="button" 지정과 onClick 핸들러 연결이 적절합니다. 이전 피드백이 반영되었습니다.


78-85: 이메일 입력: pointer-events-none 제거 권장 (접근성 개선)

readOnly만으로도 입력 방지 목적을 달성합니다. pointer-events-none은 텍스트 선택/포커스를 막아 접근성을 해칩니다.

다음과 같이 수정해 주세요.

             <EditableInputBox
                 label="이메일"
-                value={infoLoading ? '불러오는 중' : infoError ? '' : email}
-                readOnly
-                onChange={() => {}}
-                className="pointer-events-none"
+                value={infoLoading ? '불러오는 중' : infoError ? '' : email}
+                readOnly
+                onChange={() => {}}
                 placeholder="이메일"
             />
src/api/auth/account.ts (2)

7-10: changePassword가 Promise를 반환 — 훅 제네릭과 정렬 필요

useAccount의 useChangePassword 제네릭이 응답 타입으로 TChangePasswordResponse를 기대하고 있습니다. 현재 구현과 불일치합니다. 훅 제네릭의 첫 번째 타입을 void로 정렬해 주세요.

다음과 같이 수정(파일: src/hooks/auth/useAccount.ts):

- function useChangePassword(options?: TChangePasswordMutationOptions): TChangePasswordMutationResult {
-   return useCoreMutation<TChangePasswordResponse, TChangePasswordPayload>(changePassword, options);
- }
+ function useChangePassword(options?: TChangePasswordMutationOptions): TChangePasswordMutationResult {
+   return useCoreMutation<void, TChangePasswordPayload>(changePassword, options);
+ }

4-5: withCredentials 기본 설정 활용 — 개별 요청 중복 설정 없음 (좋습니다)

axiosInstance가 withCredentials: true를 기본으로 가지므로 개별 요청에서 중복 지정이 없고, 현재 구현이 간결합니다.

src/hooks/notice/useNotice.ts (4)

28-28: TypeScript 컴파일 에러 해결이 필요합니다.

typeof useCoreQuery<TFetchNoticesResponse>에서 제네릭 타입을 사용할 수 없어 빌드가 실패합니다.

다음과 같이 수정해 주세요:

-    const useGetNotices = (params: TListParams, options?: Parameters<typeof useCoreQuery<TFetchNoticesResponse>>[2]) => {
+    const useGetNotices = (params: TListParams, options?: Parameters<typeof useCoreQuery>[2]) => {

46-46: TypeScript 컴파일 에러 해결이 필요합니다.

동일한 제네릭 타입 이슈가 있습니다.

-    const useGetNoticeDetail = (id: number, options?: Parameters<typeof useCoreQuery<TFetchNoticeDetailResponse>>[2]) =>
+    const useGetNoticeDetail = (id: number, options?: Parameters<typeof useCoreQuery>[2]) =>

53-53: TypeScript 컴파일 에러 해결이 필요합니다.

동일한 제네릭 타입 이슈입니다.

-    const useSearchNotices = (params: TSearchParams, options?: Parameters<typeof useCoreQuery<TFetchNoticesResponse>>[2]) => {
+    const useSearchNotices = (params: TSearchParams, options?: Parameters<typeof useCoreQuery>[2]) => {

64-68: 훅 팩토리 패턴의 장단점 고려해 보세요.

현재 구조는 동작상 문제없지만, 개별 훅을 최상위로 내보내는 방식도 고려해 볼 만합니다.

트리 셰이킹과 IDE 지원 개선을 위해 다음과 같은 구조도 검토해 보시기 바랍니다:

export const useGetNotices = (params: TListParams, options?: Parameters<typeof useCoreQuery>[2]) => {
    // 현재 로직
};

export const useGetNoticeDetail = (id: number, options?: Parameters<typeof useCoreQuery>[2]) => {
    // 현재 로직  
};

export const useSearchNotices = (params: TSearchParams, options?: Parameters<typeof useCoreQuery>[2]) => {
    // 현재 로직
};
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 258b3a4 and 1f370cd.

📒 Files selected for processing (4)
  • src/api/auth/account.ts (1 hunks)
  • src/components/settingTab/InfoSetting.tsx (1 hunks)
  • src/components/settingTab/MembershipSetting.tsx (1 hunks)
  • src/hooks/notice/useNotice.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/api/auth/account.ts (4)
src/types/auth/account.ts (4)
  • TChangePasswordPayload (6-9)
  • TChangeNicknamePayload (12-12)
  • TChangeNicknameResponse (13-18)
  • TMemberInfo (29-40)
src/api/axiosInstance.ts (1)
  • axiosInstance (11-14)
src/types/common/common.ts (1)
  • TCommonResponse (4-9)
src/api/auth/auth.ts (5)
  • TFindPasswordValues (53-56)
  • TLoginValues (23-26)
  • TSignupValues (18-21)
  • email (43-46)
  • email (38-41)
src/hooks/notice/useNotice.ts (4)
src/hooks/customQuery.ts (1)
  • useCoreQuery (7-18)
src/types/notice/notice.ts (2)
  • TFetchNoticesResponse (12-18)
  • TFetchNoticeDetailResponse (30-30)
src/api/notice/notice.keys.ts (1)
  • noticeKeys (1-12)
src/api/notice/notice.ts (3)
  • getNotices (7-16)
  • getNoticeDetail (19-22)
  • searchNotices (25-37)
src/components/settingTab/InfoSetting.tsx (3)
src/hooks/auth/useAccount.ts (1)
  • useAccount (18-40)
src/api/auth/account.ts (1)
  • changeNickname (13-16)
src/components/common/EditableInputBox.tsx (1)
  • EditableInputBox (21-128)
src/components/settingTab/MembershipSetting.tsx (2)
src/pages/setting/PaymentHistory.tsx (3)
  • PaymentHistory (35-95)
  • page (79-87)
  • navigate (46-46)
src/components/payment/PaymentRow.tsx (1)
  • PaymentRow (11-21)
🔇 Additional comments (6)
src/components/settingTab/MembershipSetting.tsx (1)

30-30: 기본 네비게이션 분기 처리는 적절합니다

앞선 케이스에서 return 처리 후 기본 분기로 navigate(path) 하는 흐름은 명확합니다.

src/components/settingTab/InfoSetting.tsx (1)

102-121: 약관/개인정보처리방침 외부 링크 전환 — 보안 속성 포함되어 적절

a 태그로의 전환과 target="_blank" + rel="noopener noreferrer" 조합이 적절합니다. UI/접근성 모두 OK입니다.

src/hooks/notice/useNotice.ts (4)

30-37: 파라미터 안정화 구현이 우수합니다.

이전 리뷰 피드백이 정확히 반영되었습니다. 개별 값들을 의존성 배열에 포함시켜 불필요한 재렌더링을 방지하는 구조가 잘 구현되어 있습니다.


48-48: ID 검증 로직이 개선되었습니다.

이전 피드백이 반영되어 Number.isFinite(id) && id > 0로 더 엄격한 검증을 적용한 점이 좋습니다.


55-55: 검색어 전처리 로직이 적절합니다.

trim() 처리와 의존성 배열 구성이 올바르게 되어 있어 검색어의 공백 처리가 안정적입니다.


58-58: 검색 활성화 조건이 합리적입니다.

빈 검색어일 때 불필요한 API 호출을 방지하는 enabled: stable.keyword.length > 0 조건이 적절합니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🔭 Outside diff range comments (2)
src/components/common/EditableInputBox.tsx (1)

7-7: type prop의 타입 정의가 제한적입니다

현재 type prop이 'text'만 허용하도록 정의되어 있는데, 이는 확장성이 부족합니다. password, email, tel 등 다른 HTML input 타입도 지원할 수 있도록 개선이 필요합니다.

-    type?: 'text';
+    type?: React.HTMLInputTypeAttribute;

또는 필요한 타입만 제한적으로 허용하려면:

-    type?: 'text';
+    type?: 'text' | 'password' | 'email' | 'tel' | 'number';
src/routes/routes.tsx (1)

32-41: ProtectedRoute의 인증 로직이 하드코딩되어 있습니다

현재 isLoggedIn이 항상 true로 설정되어 있어 실제 인증 검증이 이루어지지 않습니다. TODO 주석이 있지만, 보안상 중요한 부분이므로 실제 인증 상태를 확인하는 로직으로 교체가 시급합니다.

실제 인증 상태를 확인하는 커스텀 훅을 사용하도록 수정이 필요합니다:

 function ProtectedRoute({ children }: PropsWithChildren) {
-    //추후 실제 로그인 여부로 대체 필요
-    const isLoggedIn = true;
+    const { isAuthenticated } = useAuth(); // 실제 인증 훅 사용
 
-    if (!isLoggedIn) {
+    if (!isAuthenticated) {
         return <Navigate to="/" replace />;
     }

인증 훅 구현이 필요하시면 도와드릴까요?

♻️ Duplicate comments (12)
src/utils/date.ts (1)

2-4: 유효하지 않은 날짜 문자열 처리 반영, 좋습니다

Falsy 입력 및 Invalid Date 가드가 추가되어 안정성이 개선되었습니다. 이전 코멘트에서 지적되던 부분이 해결되었습니다.

src/components/common/EditableInputBox.tsx (1)

17-19: readOnly prop이 이제 제대로 사용되고 있습니다!

이전 리뷰에서 지적된 readOnly prop 미사용 문제가 해결되었습니다. prop이 적절히 구조 분해 할당되고 실제로 사용되고 있습니다.

src/routes/routes.tsx (1)

98-98: 라우트 경로가 상대 경로로 올바르게 정의되었습니다

이전 리뷰에서 지적된 문제가 해결되었습니다. notice/:noticeId로 상대 경로를 사용하여 부모 라우트와 올바르게 결합됩니다.

src/components/settingTab/MembershipSetting.tsx (3)

37-47: 버튼 클릭 핸들러가 타입 기반으로 개선되었습니다

이전 리뷰에서 제안한 대로 레이블 문자열 의존성을 제거하고 타입 기반 버튼 모델로 리팩토링이 잘 완료되었습니다. disabled 속성과 state를 활용한 분기 처리가 깔끔합니다.


33-33: openMembership state가 실제로 사용되지 않을 수 있습니다

이전 리뷰에서 지적된 대로 PaymentHistory 컴포넌트에서 openMembership state를 실제로 읽지 않는다면 불필요한 state 전달입니다.

#!/bin/bash
# PaymentHistory에서 openMembership state 사용 여부 확인
echo "=== PaymentHistory.tsx에서 location.state 사용 검색 ==="
rg -A 5 "useLocation|location\.state" src/pages/setting/PaymentHistory.tsx || echo "location.state 사용 없음"

echo -e "\n=== openMembership 전체 사용처 검색 ==="
rg "openMembership" --type tsx --type ts

39-40: alert 대신 Toast 사용을 고려해보세요

이전 리뷰에서도 지적되었듯이 alert는 사용자 경험을 방해합니다. Toast나 모달 같은 비차단형 UI 컴포넌트 사용을 권장합니다.

-            alert('해당 기능은 추후 제공 예정입니다.');
+            showToast('해당 기능은 추후 제공 예정입니다.', { type: 'info' });
src/pages/setting/DeleteConfirmPage.tsx (3)

43-46: 에러 타입을 any 대신 unknown으로 변경하세요

타입 안전성을 위해 any 대신 unknown을 사용하고 적절한 타입 가드를 추가하는 것이 좋습니다.

-        onError: (error) => {
-            const msg = error?.response?.data?.message || '회원 탈퇴에 실패했습니다.';
+        onError: (error: unknown) => {
+            const msg = (error as any)?.response?.data?.message || '회원 탈퇴에 실패했습니다.';
             alert(msg);
         },

더 나은 방법은 AxiosError 타입 가드를 사용하는 것입니다:

+import { AxiosError } from 'axios';

         onError: (error: unknown) => {
-            const msg = error?.response?.data?.message || '회원 탈퇴에 실패했습니다.';
+            let msg = '회원 탈퇴에 실패했습니다.';
+            if (error instanceof AxiosError && error.response?.data?.message) {
+                msg = error.response.data.message;
+            }
             alert(msg);
         },

51-55: 사용자 경험 개선을 위해 커스텀 모달 사용을 고려하세요

브라우저 기본 confirm 대신 앱의 디자인과 일치하는 커스텀 모달을 사용하면 더 나은 사용자 경험을 제공할 수 있습니다.


120-121: Tailwind CSS 표준 클래스를 사용하세요

rounding-32는 Tailwind CSS 표준 클래스가 아닙니다. rounded-full 또는 rounded-[32px]를 사용해야 합니다.

-            className={`px-8 py-3 rounding-32 font-body1 text-white
+            className={`px-8 py-3 rounded-full font-body1 text-white
                 ${allAgreed && !isPending ? 'bg-primary-500 hover:bg-primary-600' : 'bg-default-gray-400 cursor-not-allowed'}`}
src/components/settingTab/InfoSetting.tsx (3)

98-98: pointer-events-none 클래스를 제거하세요

readOnly 속성만으로도 입력을 방지할 수 있으며, pointer-events-none은 텍스트 선택과 접근성을 해칠 수 있습니다.

-                className="pointer-events-none"

41-42: 로그아웃 시 localStorage ‘nickname’ 제거 로직 추가 필요

현재 useLogout 훅을 호출하고 있지만, 로그아웃 시 localStorage에 남아 있는 nickname을 제거하는 코드가 없습니다. 아래 위치에 localStorage.removeItem('nickname')를 추가해주세요.

  • 위치: src/hooks/auth/useAuth.ts
    • useLogout = useCoreMutation(logout) 정의부

예시(diff):

--- a/src/hooks/auth/useAuth.ts
+++ b/src/hooks/auth/useAuth.ts
@@ -10,7 +10,13 @@ export function useAuth() {
-  const useLogout = useCoreMutation(logout);
+  const useLogout = useCoreMutation(logout, {
+    onSuccess: () => {
+      // 로그아웃 후 닉네임 제거
+      localStorage.removeItem('nickname');
+    },
+  });

   return {
     useLogout,

29-33: 빈 문자열도 유효한 값으로 처리하도록 조건문을 수정하세요

현재 apiNickname이 빈 문자열일 때 상태가 업데이트되지 않습니다. 서버에서 빈 문자열을 반환할 수 있다면 이를 정상 값으로 처리해야 합니다.

     useEffect(() => {
-        if (apiNickname) {
+        if (apiNickname !== undefined && apiNickname !== null) {
             setNickname(apiNickname);
             setInitialNickname(apiNickname);
         }
     }, [apiNickname]);

또는 기본값 할당을 제거하는 방법도 있습니다:

-    const apiNickname = memberData?.result?.username ?? '';
+    const apiNickname = memberData?.result?.username;
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f370cd and 8768c7e.

📒 Files selected for processing (11)
  • src/api/auth/account.ts (1 hunks)
  • src/api/dates/preferences.ts (1 hunks)
  • src/components/common/EditableInputBox.tsx (4 hunks)
  • src/components/settingTab/InfoSetting.tsx (1 hunks)
  • src/components/settingTab/MembershipSetting.tsx (1 hunks)
  • src/hooks/auth/useAccount.ts (1 hunks)
  • src/pages/setting/DeleteConfirmPage.tsx (4 hunks)
  • src/routes/routes.tsx (2 hunks)
  • src/types/auth/account.ts (1 hunks)
  • src/types/dates/preferences.ts (1 hunks)
  • src/utils/date.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (11)
src/api/dates/preferences.ts (3)
src/types/dates/preferences.ts (1)
  • TResetPreferencesResponse (3-3)
src/api/axiosInstance.ts (1)
  • axiosInstance (11-14)
src/api/home/dateTimes.ts (2)
  • Promise (11-14)
  • Promise (6-9)
src/types/dates/preferences.ts (1)
src/types/common/common.ts (1)
  • TCommonResponse (4-9)
src/api/auth/account.ts (3)
src/types/auth/account.ts (5)
  • TChangePasswordPayload (6-9)
  • TChangeNicknamePayload (12-12)
  • TChangeNicknameResponse (13-18)
  • TMemberInfo (29-40)
  • TGetMemberGradeResponse (51-51)
src/api/axiosInstance.ts (1)
  • axiosInstance (11-14)
src/types/common/common.ts (1)
  • TCommonResponse (4-9)
src/components/settingTab/InfoSetting.tsx (4)
src/hooks/auth/useAccount.ts (1)
  • useAccount (19-51)
src/api/auth/account.ts (1)
  • changeNickname (13-16)
src/components/common/EditableInputBox.tsx (1)
  • EditableInputBox (22-164)
src/pages/TestInputPage.tsx (2)
  • setNickname (40-40)
  • console (41-41)
src/pages/setting/DeleteConfirmPage.tsx (4)
src/hooks/auth/useAccount.ts (1)
  • useAccount (19-51)
src/pages/auth/UserSetting.tsx (1)
  • User (23-178)
src/pages/auth/FindPw.tsx (2)
  • FindPw (17-207)
  • data (109-116)
src/hooks/auth/useAuth.ts (1)
  • useAuth (5-14)
src/utils/date.ts (3)
src/utils/getTodayString.ts (1)
  • getTodayString (1-7)
src/utils/formatDateInput.tsx (1)
  • formatDateInput (1-11)
src/utils/getNextSevenDay.ts (1)
  • getNextSevenDay (1-14)
src/components/settingTab/MembershipSetting.tsx (4)
src/hooks/auth/useAccount.ts (1)
  • useAccount (19-51)
src/components/common/ToggleSwitch.tsx (1)
  • ToggleSwitch (11-48)
src/pages/home/HomePage.tsx (1)
  • Home (16-60)
src/hooks/home/useUserGrade.ts (1)
  • getUserGrade (8-8)
src/routes/routes.tsx (3)
src/pages/setting/PaymentHistory.tsx (3)
  • PaymentHistory (35-95)
  • navigate (46-46)
  • page (79-87)
src/pages/setting/DeleteConfirmPage.tsx (1)
  • DeleteConfirmPage (26-130)
src/pages/notice/Notice.tsx (1)
  • Notice (13-119)
src/types/auth/account.ts (1)
src/types/common/common.ts (2)
  • TUseMutationCustomOptions (17-20)
  • TCommonResponse (4-9)
src/components/common/EditableInputBox.tsx (2)
src/pages/TestInputPage.tsx (2)
  • TestInputPage (8-46)
  • e (39-39)
src/pages/auth/UserSetting.tsx (1)
  • field (132-144)
src/hooks/auth/useAccount.ts (6)
src/types/auth/account.ts (8)
  • TChangePasswordMutationOptions (21-21)
  • TChangePasswordMutationResult (22-22)
  • TChangePasswordResponse (10-10)
  • TChangePasswordPayload (6-9)
  • TChangeNicknameMutationOptions (25-25)
  • TChangeNicknameMutationResult (26-26)
  • TChangeNicknameResponse (13-18)
  • TChangeNicknamePayload (12-12)
src/hooks/customQuery.ts (2)
  • useCoreMutation (21-29)
  • useCoreQuery (7-18)
src/api/auth/account.ts (5)
  • changePassword (7-10)
  • changeNickname (13-16)
  • deleteMember (19-21)
  • getMemberInfo (24-27)
  • getMemberGrade (30-33)
src/types/common/common.ts (1)
  • TUseMutationCustomOptions (17-20)
src/types/dates/preferences.ts (1)
  • TResetPreferencesResponse (3-3)
src/api/dates/preferences.ts (1)
  • resetPreferences (6-9)
🪛 Biome (2.1.2)
src/components/common/EditableInputBox.tsx

[error] 126-133: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🔇 Additional comments (9)
src/utils/date.ts (1)

5-9: 표시 형식 확인 요청: 'YYYY. MM. DD.' vs 'YYYY.MM.DD'

toLocaleDateString('ko-KR', { year:'numeric', month:'2-digit', day:'2-digit' })는 일반적으로 공백과 마지막 마침표가 포함된 "YYYY. MM. DD." 형태를 반환합니다. 디자인/기획에서 공백 없는 "YYYY.MM.DD"를 원하신다면 현재 구현과 UI 표준이 불일치할 수 있습니다. src/utils/formatDateInput.tsx가 "YYYY.MM.DD"로 포맷팅하는 점과도 비교해 일관성을 결정해 주세요.

원하신다면 다음과 같이 formatToParts를 사용해 공백/마침표 없이 안정적으로 구성할 수 있습니다(타임존 포함):

-    return date.toLocaleDateString('ko-KR', {
-        year: 'numeric',
-        month: '2-digit',
-        day: '2-digit',
-        timeZone: 'Asia/Seoul',
-    });
+    const parts = new Intl.DateTimeFormat('ko-KR', {
+        year: 'numeric',
+        month: '2-digit',
+        day: '2-digit',
+        timeZone: 'Asia/Seoul',
+    }).formatToParts(date);
+    const y = parts.find(p => p.type === 'year')?.value ?? '';
+    const m = parts.find(p => p.type === 'month')?.value ?? '';
+    const d = parts.find(p => p.type === 'day')?.value ?? '';
+    return y && m && d ? `${y}.${m}.${d}` : '';

디자인 기대 형식을 알려주시면 그에 맞춰 최적의 구현으로 정리해 드리겠습니다.

src/components/common/EditableInputBox.tsx (1)

45-56: useEffect 내 requestAnimationFrame 사용이 적절합니다

편집 모드 진입 시 자동 포커스와 텍스트 선택 기능이 잘 구현되었습니다. requestAnimationFrame을 사용하여 DOM 업데이트 후 포커스를 설정하는 것은 좋은 접근입니다.

src/types/dates/preferences.ts (1)

3-3: 빈 응답을 위한 타입 정의가 적절합니다

Record<string, never>를 사용하여 빈 객체를 타입으로 정의한 것은 좋은 접근입니다. 이는 타입 안전성을 유지하면서 빈 result를 명확하게 표현합니다.

src/api/dates/preferences.ts (1)

5-9: DELETE 메소드 사용이 RESTful 원칙에 적합합니다

취향 데이터 초기화를 위해 DELETE 메소드를 사용한 것은 RESTful API 설계 원칙에 잘 부합합니다. 타입 정의도 적절히 되어 있습니다.

src/routes/routes.tsx (1)

141-145: ProtectedRoute 래핑이 일관성 있게 적용되었습니다

결제 내역, 계정 삭제 관련 페이지들이 모두 ProtectedRoute로 적절히 보호되고 있습니다. 인증이 필요한 페이지들의 접근 제어가 잘 구현되었습니다.

Also applies to: 149-153, 157-161

src/components/settingTab/MembershipSetting.tsx (1)

13-15: API 에러 처리가 적절합니다

등급 조회 실패 시 '조회 실패' 텍스트를 표시하는 것은 좋은 에러 처리입니다. 사용자에게 친화적인 피드백을 제공합니다.

src/api/auth/account.ts (2)

7-10: 비밀번호 변경 함수가 void를 반환하도록 정의되었습니다

API 구현이 올바르게 되어 있습니다. 서버가 204 No Content를 반환하는 경우 void 반환이 적절합니다.


13-16: axiosInstance에 이미 401 Unauthorized 처리 로직이 구현되어 있습니다

  • src/api/axiosInstance.ts의 interceptors.response.use 내부에서
    error.response?.data.error === 'Unauthorized' 패턴으로 401 에러를 전역 처리하고 있습니다.
  • changeNickname 함수는 에러를 그대로 전파하도록 작성되어 있어, 별도의 401 처리 로직 추가가 불필요합니다.

이로써 해당 PR의 API 레이어에서는 개별적으로 401을 다루지 않아도 되므로, 원コメント은 적용할 필요가 없습니다.

Likely an incorrect or invalid review comment.

src/types/auth/account.ts (1)

1-51: 타입 정의가 명확하고 잘 구조화되어 있습니다

API 페이로드, 응답, 그리고 뮤테이션 관련 타입들이 체계적으로 정의되어 있습니다. 특히 TChangePasswordResponsevoid로 정의한 것이 실제 API 구현과 일치합니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🔭 Outside diff range comments (2)
src/types/notice/notice.ts (1)

11-18: 목록 응답 타입 구조 OK + 재사용성 고려 제안

search 응답도 동일 구조를 반환한다면 현재 제네릭 래핑은 합리적입니다. 다만 Notice 목록 페이지네이션 페이로드를 별도 타입으로 분리해 재사용성을 높일 수 있습니다.

아래처럼 서브 타입을 분리하면 여러 API(search, list)가 동일한 result 구조를 공유할 때 유지보수가 편해집니다.

-export type TFetchNoticesResponse = TCommonResponse<{
-    noticeList: TNoticeItem[];
-    totalPages: number;
-    currentPage: number;
-    currentSize: number;
-    hasNextPage: boolean;
-}>;
+type TNoticeListPage = {
+  noticeList: TNoticeItem[];
+  totalPages: number;
+  currentPage: number;
+  currentSize: number;
+  hasNextPage: boolean;
+};
+export type TFetchNoticesResponse = TCommonResponse<TNoticeListPage>;
src/components/common/EditableInputBox.tsx (1)

155-160: 취소/완료 버튼에 type="button" 추가 필요

폼 내부에서 의도하지 않은 제출을 방지하기 위해 명시적인 type 속성이 필요합니다.

                 <div className="flex justify-end gap-2 mt-3">
-                    <button onClick={handleCancel} className="font-body1 px-4 py-1.5 rounded-full bg-default-gray-400 text-default-gray-700">
+                    <button type="button" onClick={handleCancel} className="font-body1 px-4 py-1.5 rounded-full bg-default-gray-400 text-default-gray-700">
                         취소
                     </button>
-                    <button onClick={handleSubmit} className="font-body1 px-4 py-1.5 rounded-full bg-default-gray-400 text-default-gray-700">
+                    <button type="button" onClick={handleSubmit} className="font-body1 px-4 py-1.5 rounded-full bg-default-gray-400 text-default-gray-700">
                         완료
                     </button>
                 </div>
♻️ Duplicate comments (6)
src/api/notice/notice.ts (1)

24-37: search 파라미터 타입 분리로 일관성/재사용성 개선

인라인 타입 대신 별도 타입으로 분리해 재사용성과 가독성을 높이는 것이 좋습니다. 기존 리뷰에서도 동일 제안이 있었습니다.

아래처럼 타입을 분리해 주세요.

-export const searchNotices = async (params: { keyword: string; page: number; size: number; category?: 'SERVICE' | 'SYSTEM' }) => {
+export type TSearchNoticesParams = {
+  keyword: string;
+  page: number;
+  size: number;
+  category?: 'SERVICE' | 'SYSTEM';
+};
+export const searchNotices = async (params: TSearchNoticesParams) => {
     const { keyword, page, size, category } = params;
src/components/settingTab/InfoSetting.tsx (4)

23-33: apiNickname 동기화 조건 수정 필요(빈 문자열 처리 누락)

빈 문자열('')일 경우 useEffect가 동작하지 않아 상태가 갱신되지 않습니다. 기본값 할당을 제거하고 undefined/null 여부만 판단하도록 변경하세요.

아래처럼 수정 제안드립니다.

-    const apiNickname = memberData?.result?.username ?? '';
+    const apiNickname = memberData?.result?.username;

     useEffect(() => {
-        if (apiNickname) {
+        // apiNickname이 빈 문자열이어도 상태에 반영
+        if (apiNickname !== undefined && apiNickname !== null) {
             setNickname(apiNickname);
             setInitialNickname(apiNickname);
         }
     }, [apiNickname]);

95-99: 읽기 전용 이메일 입력에 pointer-events-none 제거 권장

readOnly만으로 입력 방지가 충분합니다. pointer-events-none은 텍스트 복사/포커스를 막아 접근성을 떨어뜨립니다.

아래처럼 className을 제거해 주세요.

             <EditableInputBox
                 label="이메일"
-                value={infoLoading ? '불러오는 중' : infoError ? '' : email}
-                readOnly
-                onChange={() => {}}
-                className="pointer-events-none"
+                value={infoLoading ? '불러오는 중' : infoError ? '' : email}
+                readOnly
+                onChange={() => {}}
                 placeholder="이메일"
             />

111-111: Tailwind 클래스 오타: rounding-32 → rounded-[32px] (또는 rounded-full)

Tailwind 표준 유틸이 아닙니다. 의도대로 32px 라운딩을 유지하려면 arbitrary value를 사용하세요.

-                    className={`bg-primary-500 px-6 py-2 rounding-32 text-white font-body1 ${resetPending ? 'opacity-60 cursor-not-allowed' : ''}`}
+                    className={`bg-primary-500 px-6 py-2 rounded-[32px] text-white font-body1 ${resetPending ? 'opacity-60 cursor-not-allowed' : ''}`}

35-48: 로그아웃 시 로컬스토리지 닉네임 제거 필요

현재 InfoSetting.tsx에서 닉네임을 localStorage.setItem('nickname', …)으로만 저장하고, 로그아웃 시 해당 키를 지우는 로직이 없습니다. 로그아웃 처리(예: 로그아웃 API 호출 후 성공 콜백) 부분에 반드시 localStorage.removeItem('nickname')을 추가해 주세요.

수정 위치 예시

  • src/components/settingTab/InfoSetting.tsx
  • 로그아웃 처리 컴포넌트/훅 (예: useAuth.ts에서 useLogout을 사용하는 곳)

예시 diff

// 로그아웃 성공 이후
onSuccess: () => {
+   localStorage.removeItem('nickname');
    // …기존 로그아웃 후 처리 로직
}
src/hooks/auth/useAccount.ts (1)

19-23: QUERY_KEYS는 모듈 스코프로 이동/공유하세요

훅 호출 시마다 객체가 재생성됩니다. 모듈 상단으로 이동하고(또는 공용 constants 폴더로 분리) 내외부에서 동일 상수를 사용하도록 해 주세요. InfoSetting의 invalidateQueries에서도 재사용 가능합니다.

아래처럼 이동/정리 제안드립니다.

+// 파일 상단(모듈 스코프)으로 이동
+const QUERY_KEYS = {
+    memberInfo: ['memberInfo'] as const,
+    memberGrade: ['memberGrade'] as const,
+} as const;

 export function useAccount() {
-    const QUERY_KEYS = {
-        memberInfo: ['memberInfo'] as const,
-        memberGrade: ['memberGrade'] as const,
-    } as const;

또는 src/constants/queryKeys.ts로 분리 후 import하여 사용하세요.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8768c7e and 514d402.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock, !**/*.lock
📒 Files selected for processing (11)
  • src/api/notice/notice.ts (1 hunks)
  • src/components/common/EditableInputBox.tsx (4 hunks)
  • src/components/settingTab/InfoSetting.tsx (1 hunks)
  • src/constants/policies.ts (1 hunks)
  • src/firebase/firebase.ts (1 hunks)
  • src/hooks/auth/useAccount.ts (1 hunks)
  • src/hooks/customQuery.ts (1 hunks)
  • src/pages/home/HomePage.tsx (2 hunks)
  • src/types/auth/account.ts (1 hunks)
  • src/types/notice/notice.ts (2 hunks)
  • src/utils/date.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (9)
src/hooks/customQuery.ts (1)
src/types/common/common.ts (1)
  • TUseMutationCustomOptions (17-20)
src/hooks/auth/useAccount.ts (5)
src/types/auth/account.ts (7)
  • TChangePasswordMutationOptions (20-20)
  • TChangePasswordMutationResult (21-21)
  • TChangePasswordPayload (6-9)
  • TChangeNicknameMutationOptions (24-24)
  • TChangeNicknameMutationResult (25-25)
  • TChangeNicknameResponse (12-17)
  • TChangeNicknamePayload (11-11)
src/hooks/customQuery.ts (2)
  • useCoreMutation (21-29)
  • useCoreQuery (7-18)
src/api/auth/account.ts (5)
  • changePassword (7-10)
  • changeNickname (13-16)
  • deleteMember (19-21)
  • getMemberInfo (24-27)
  • getMemberGrade (30-33)
src/types/common/common.ts (1)
  • TUseMutationCustomOptions (17-20)
src/types/dates/preferences.ts (1)
  • TResetPreferencesResponse (3-3)
src/utils/date.ts (4)
src/utils/getTodayString.ts (1)
  • getTodayString (1-7)
src/utils/formatDateInput.tsx (1)
  • formatDateInput (1-11)
src/utils/getNextSevenDay.ts (1)
  • getNextSevenDay (1-14)
src/pages/auth/UserSetting.tsx (1)
  • e (109-112)
src/pages/home/HomePage.tsx (2)
src/hooks/home/useUserGrade.ts (2)
  • getUserGrade (8-8)
  • useCoreQuery (7-12)
src/hooks/alarm/useDeviceToken.ts (3)
  • window (35-37)
  • useEffect (8-39)
  • setupFCM (9-38)
src/types/auth/account.ts (1)
src/types/common/common.ts (2)
  • TUseMutationCustomOptions (17-20)
  • TCommonResponse (4-9)
src/api/notice/notice.ts (6)
src/api/axiosInstance.ts (1)
  • axiosInstance (11-14)
src/types/notice/notice.ts (2)
  • TFetchNoticesResponse (12-18)
  • TFetchNoticeDetailResponse (30-30)
src/pages/notice/Notice.tsx (3)
  • Notice (13-119)
  • getNotices (29-55)
  • setLoading (30-52)
src/pages/notice/NoticeDetail.tsx (2)
  • noticeId (17-42)
  • NoticeDetail (8-69)
src/hooks/notices/useGetNotices.ts (2)
  • TRequestGetNoticeRequest (8-15)
  • page (11-11)
src/components/home/info.tsx (1)
  • MainInfo (10-63)
src/types/notice/notice.ts (5)
src/types/common/common.ts (1)
  • TCommonResponse (4-9)
src/pages/notice/Notice.tsx (4)
  • Notice (13-119)
  • getNotices (29-55)
  • notice (97-104)
  • setLoading (30-52)
src/hooks/notices/useGetNotices.ts (1)
  • TRequestGetNoticeRequest (8-15)
src/pages/notice/NoticeDetail.tsx (2)
  • NoticeDetail (8-69)
  • noticeId (17-42)
src/components/home/info.tsx (1)
  • MainInfo (10-63)
src/components/common/EditableInputBox.tsx (2)
src/pages/TestInputPage.tsx (2)
  • TestInputPage (8-46)
  • e (39-39)
src/pages/auth/UserSetting.tsx (1)
  • field (132-144)
src/components/settingTab/InfoSetting.tsx (5)
src/hooks/auth/useAccount.ts (1)
  • useAccount (18-55)
src/api/auth/account.ts (1)
  • changeNickname (13-16)
src/components/common/EditableInputBox.tsx (1)
  • EditableInputBox (22-165)
src/constants/policies.ts (2)
  • TERMS_URL (1-1)
  • PRIVACY_URL (2-2)
src/pages/TestInputPage.tsx (2)
  • setNickname (40-40)
  • console (41-41)
🪛 GitHub Check: Lint
src/firebase/firebase.ts

[failure] 5-5:
Delete

src/pages/home/HomePage.tsx

[failure] 7-7:
Delete

src/api/notice/notice.ts

[failure] 37-37:
Insert

src/types/notice/notice.ts

[failure] 30-30:
Insert

🪛 GitHub Actions: CI
src/api/notice/notice.ts

[error] 37-37: prettier/prettier: Insert

🔇 Additional comments (9)
src/types/notice/notice.ts (1)

3-9: 타입 정의 명확성 좋아요

공지 항목(TNoticeItem) 구조가 간결하고 필요한 필드만 포함하고 있어 사용처 가독성에 도움이 됩니다.

src/utils/date.ts (1)

1-11: 날짜 포맷팅 유틸리티가 잘 구현되었습니다!

유효하지 않은 날짜에 대한 적절한 에러 처리와 Asia/Seoul 타임존 명시로 일관된 날짜 표시를 보장합니다.

src/constants/policies.ts (1)

1-2: URL 상수가 올바르게 추가되었습니다

이용약관과 개인정보 처리방침 URL이 적절히 정의되었습니다.

src/hooks/customQuery.ts (1)

20-21: 타입 안정성이 개선되었습니다

useCoreMutation의 options 파라미터에 제네릭 타입을 적용하여 타입 안정성이 향상되었습니다. 이제 mutation의 데이터와 변수 타입이 options와 일치하도록 보장됩니다.

src/components/settingTab/InfoSetting.tsx (2)

61-66: 닉네임 제출 로직 깔끔합니다

trim, 동일값/진행중 차단 등 기본 검증이 잘 반영되어 있습니다.


118-136: 약관/개인정보 처리방침 링크 처리 LGTM

a 태그로 target/rel 포함한 외부 링크 처리 적절합니다. 접근성/보안 고려(noopener)도 잘 적용되었습니다.

src/hooks/auth/useAccount.ts (2)

25-27: changePassword 제네릭 void 정합성 OK

API가 Promise를 반환하는 것과 제네릭이 일치합니다.


49-52: resetPreferences 직접 전달로 래퍼 제거한 점 좋습니다

불필요한 래퍼 없이 시그니처에 맞춰 직접 전달되어 가독성과 타입 안정성이 향상되었습니다.

src/types/auth/account.ts (1)

6-17: 페이로드/응답 타입 정의 적절합니다

API 시그니처와 일치하며, 닉네임 응답 구조도 명확합니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

🔭 Outside diff range comments (4)
src/hooks/alarm/useDeviceToken.ts (1)

1-40: 주석된 useDeviceToken 훅 정리 및 no-op 버전으로 교체

현재 src/hooks/alarm/useDeviceToken.ts 파일 전체가 주석 처리되어 있고, 실제 코드베이스에서는 HomePage.tsx에서만 호출문이 주석으로 남아 있습니다. 유지보수성과 린터 이슈를 해결하기 위해 아래와 같이 정리해 주세요.

  • 확인된 사용처
    • src/pages/home/HomePage.tsx
      • 16행: // useDeviceToken(); 호출문만 존재(주석 처리)

제안 수정안:

-// src/hooks/alarm/useDeviceToken.ts
-// import { useEffect } from 'react';
-// import { isSupported } from 'firebase/messaging';
-// import { postDeviceToken } from '@/api/alarm/alarm'; 
-// import { generateToken, registerServiceWorker } from '@/firebase/firebase';
-//
-// export const useDeviceToken = () => {
-//   useEffect(() => {
-//     /* FCM 토큰 등록 로직 */
-//   }, []);
-// };
+// src/hooks/alarm/useDeviceToken.ts
+// 임시 비활성화: FCM 디바이스 토큰 등록 API가 401을 반환하여 로직 보류 (#90)
+export const useDeviceToken = (): void => {
+  // TODO(#90): FCM 토큰 등록 로직 재활성화
+};

이제 불필요한 주석 코드를 제거하고, 동일 시그니처의 no-op 훅을 유지해 사용처가 깨지지 않도록 해주세요.

src/components/common/EditableInputBox.tsx (1)

67-71: 검색 인풋에서 Enter 입력 시 기본 제출 방지 필요

폼 내부 사용 시 의도치 않은 submit을 유발할 수 있습니다. e.preventDefault()를 추가해 주세요.

     const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
         if (e.key === 'Enter' && isSearch) {
-            onSearchClick?.();
+            e.preventDefault();
+            onSearchClick?.();
         }
     };
src/pages/setting/DeleteReasonPage.tsx.tsx (2)

5-6: 컴포넌트 파일명 오타 수정 필요

현재 src/components/common/ReansonButton.tsx 파일명이 오타로 인해 잘못 지정되어 있습니다. 아래 항목을 반영해 파일명과 import 구문을 일치시켜 주세요.

  • 파일명 변경
    • src/components/common/ReansonButton.tsx → src/components/common/ReasonButton.tsx
  • import 구문 수정 (DeleteReasonPage.tsx.tsx 5행)
    - import ReasonButton from '@/components/common/ReansonButton';
    + import ReasonButton from '@/components/common/ReasonButton';

1-1: 파일명 확장자 중복(.tsx.tsx) 수정 및 import 경로 동기화 필요

중복된 .tsx.tsx 확장자로 인해 에디터나 번들러가 파일을 인식하지 못할 수 있습니다. 아래 위치를 수정해 주세요:

• 파일 리네임

  • 변경 전: src/pages/setting/DeleteReasonPage.tsx.tsx
  • 변경 후: src/pages/setting/DeleteReasonPage.tsx

• import 경로 수정 (src/routes/routes.tsx 29행)

- import DeleteReasonPage from '@/pages/setting/DeleteReasonPage.tsx';
+ import DeleteReasonPage from '@/pages/setting/DeleteReasonPage';

이후 관련 경로들이 정상 동작하는지 빌드 및 에디터에서 확인 부탁드립니다.

♻️ Duplicate comments (32)
src/pages/home/HomePage.tsx (1)

16-16: 주석 처리된 호출은 맥락을 남기거나 제거해 주세요

일시 비활성화라면 이유(401/이슈 번호)를 명시한 TODO로 남겨 주세요. 불필요하면 삭제가 깔끔합니다.

가능한 수정안:

-    // useDeviceToken();
+    // TODO(#90): 알림(FCM) 디바이스 토큰 전송 API가 401을 반환하여 임시 비활성화. 이슈 해결 후 재활성화.
+    // useDeviceToken();
src/api/notice/notice.keys.ts (1)

5-12: 검색/목록 키에 객체를 직접 넣으면 캐시 키가 매 호출마다 달라집니다 — 원시 파라미터로 분리하세요

React Query 키에 객체 참조를 포함하면 렌더마다 새로운 레퍼런스가 생겨 캐시 적중률이 떨어지고 불필요한 refetch를 유발합니다. keyword/page/size/category를 개별 인자로 받아 키를 구성하는 방식이 안전합니다.

아래와 같이 수정 제안드립니다.

 export const noticeKeys = {
     root: ['notice'] as const, // 루트 키

     // 목록 조회 키
-    list: (p: { category: 'SERVICE' | 'SYSTEM'; page: number; size: number }) => [...noticeKeys.root, 'list', p] as const,
+    list: (category: 'SERVICE' | 'SYSTEM', page: number, size: number) =>
+        [...noticeKeys.root, 'list', category, page, size] as const,

     // 상세 조회 키
     detail: (id: number) => [...noticeKeys.root, 'detail', id] as const,

     // 검색 조회 키
-    search: (p: { keyword: string; page: number; size: number; category?: 'SERVICE' | 'SYSTEM' }) => [...noticeKeys.root, 'search', p] as const,
+    search: (keyword: string, page: number, size: number, category?: 'SERVICE' | 'SYSTEM') =>
+        [...noticeKeys.root, 'search', keyword, page, size, ...(category ? [category] : [])] as const,
 };

호출부도 객체 대신 개별 인자로 넘기도록 함께 정리해 주세요.

src/components/faq/FAQItem.tsx (1)

19-37: role="region" 제거 및 버튼-패널 연계 라벨링 추가 필요

Biome가 지적하듯 section에 role="region"은 불필요하며, 이름 없는 landmark 이슈를 만듭니다. 버튼에 id를 부여하고 section에 aria-labelledby로 연결해 주세요.

-            <button type="button" onClick={onToggle} className="flex w-full items-center gap-4 text-left" aria-expanded={isOpen} aria-controls={contentId}>
+            <button
+                id={`faq-toggle-${item.faqId}`}
+                type="button"
+                onClick={onToggle}
+                className="flex w-full items-center gap-4 text-left"
+                aria-expanded={isOpen}
+                aria-controls={contentId}
+            >
...
-            <section
-                id={contentId}
-                role="region"
-                aria-hidden={!isOpen}
+            <section
+                id={contentId}
+                aria-labelledby={`faq-toggle-${item.faqId}`}
+                aria-hidden={!isOpen}
                 className={`ml-0 sm:ml-9 overflow-hidden transition-[max-height,opacity] duration-200 ease-in-out ${
                     isOpen ? 'max-h-[1000px] opacity-100 mt-4' : 'max-h-0 opacity-0'
                 }`}
             >
src/components/common/EditableInputBox.tsx (1)

149-155: 취소/완료 버튼에도 type="button"을 명시하세요

기본 type=submit로 인해 폼 내에서 의도치 않은 제출이 발생할 수 있습니다.

-                    <button onClick={handleCancel} className="font-body1 px-4 py-1.5 rounded-full bg-default-gray-400 text-default-gray-700">
+                    <button type="button" onClick={handleCancel} className="font-body1 px-4 py-1.5 rounded-full bg-default-gray-400 text-default-gray-700">
                         취소
                     </button>
-                    <button onClick={handleSubmit} className="font-body1 px-4 py-1.5 rounded-full bg-default-gray-400 text-default-gray-700">
+                    <button type="button" onClick={handleSubmit} className="font-body1 px-4 py-1.5 rounded-full bg-default-gray-400 text-default-gray-700">
                         완료
                     </button>
src/pages/setting/DeleteReasonPage.tsx.tsx (1)

32-34: 뒤로가기: 히스토리 우선, 홈 폴백으로 개선

딥링크/다단계 진입 시 컨텍스트 유지를 위해 history back을 우선하고, 없을 때만 홈으로 이동하세요. 접근성 라벨과 버튼 type도 함께 명시합니다.

-                        <button onClick={() => navigate('/home')}>
+                        <button
+                            type="button"
+                            aria-label="뒤로가기"
+                            onClick={() => (window.history.length > 1 ? navigate(-1) : navigate('/home'))}
+                        >
src/routes/routes.tsx (1)

98-98: 라우트 경로 불일치 문제

notice/:noticeId 경로가 상대 경로로 올바르게 정의되어 있습니다. 이전 리뷰에서 지적된 절대 경로 문제가 해결되었습니다.

src/types/notice/notice.ts (1)

39-39: 파일 끝에 개행 문자를 추가하세요

Prettier 규칙에 따라 파일 끝에 개행 문자가 필요합니다.

-export type TFetchNoticeDetailResponse = TCommonResponse<TNoticeDetail | null>;
+export type TFetchNoticeDetailResponse = TCommonResponse<TNoticeDetail | null>;
+
src/api/settingAlarm/alarm.ts (1)

1-15: React Query 취소 신호(AbortSignal) 전달을 위해 AxiosRequestConfig 지원을 추가해 주세요

쿼리 취소/중복 방지를 위해 API 함수가 signal을 전달받을 수 있어야 합니다. 선택적 config를 받아 axios로 넘기도록 확장하는 것을 권장합니다.

아래와 같이 수정 제안 드립니다:

 import type { TAlarmSettings, TGetAlarmSettingsResp, TPatchAlarmSettingsResp } from '@/types/settingAlarm/alarm';
 
 import { axiosInstance } from '@/api/axiosInstance';
+import type { AxiosRequestConfig } from 'axios';
 
 // 조회
-export async function getAlarmSettings(): Promise<TGetAlarmSettingsResp> {
-    const { data } = await axiosInstance.get<TGetAlarmSettingsResp>('/api/v1/alarms/settings');
+export async function getAlarmSettings(config?: AxiosRequestConfig): Promise<TGetAlarmSettingsResp> {
+    const { data } = await axiosInstance.get<TGetAlarmSettingsResp>('/api/v1/alarms/settings', { ...config });
     return data;
 }
 
 // 업데이트
-export async function patchAlarmSettings(payload: TAlarmSettings): Promise<TPatchAlarmSettingsResp> {
-    const { data } = await axiosInstance.patch<TPatchAlarmSettingsResp>('/api/v1/alarms/settings', payload);
+export async function patchAlarmSettings(payload: TAlarmSettings, config?: AxiosRequestConfig): Promise<TPatchAlarmSettingsResp> {
+    const { data } = await axiosInstance.patch<TPatchAlarmSettingsResp>('/api/v1/alarms/settings', payload, { ...config });
     return data;
 }
src/hooks/settingAlarm/useAlarms.ts (3)

12-16: React Query의 AbortSignal을 API로 전달해 네트워크 낭비를 줄여주세요

useCoreQueryqueryFn에 전달되는 { signal }을 axios에 넘기면 탭 전환 등에서 불필요한 요청을 취소할 수 있습니다. API 레이어가 config?: AxiosRequestConfig를 받도록 확장한 뒤, 아래처럼 연결해 주세요.

-import { getAlarmSettings, patchAlarmSettings } from '@/api/settingAlarm/alarm';
+import { getAlarmSettings, patchAlarmSettings } from '@/api/settingAlarm/alarm';
+import { alarmSettingsKeys } from '@/api/settingAlarm/alarm.keys';
 
 // 조회
 export function useGetAlarmSettings() {
-    return useCoreQuery<TGetAlarmSettingsResp, TAlarmSettings>(['alarmSettings'], getAlarmSettings, {
+    return useCoreQuery<TGetAlarmSettingsResp, TAlarmSettings>(alarmSettingsKeys.all, ({ signal }) => getAlarmSettings({ signal }), {
         select: (resp) => resp.result,
         refetchOnWindowFocus: false,
     });
 }

참고: alarmSettingsKeys는 아래 코멘트의 추가 파일을 참고해 주세요.


21-25: invalidateQueries 대신 setQueryData로 즉시 캐시 반영(UX 개선)

PATCH 응답에 최종 설정값이 포함되므로, 무효화→재요청 대신 캐시를 직접 갱신하면 UI가 즉시 반영됩니다.

 export function usePatchAlarmSettings() {
     const qc = useQueryClient();
     return useCoreMutation<TPatchAlarmSettingsResp, TAlarmSettings>(patchAlarmSettings, {
-        onSuccess: () => {
-            qc.invalidateQueries({ queryKey: ['alarmSettings'] });
-        },
+        onSuccess: (resp) => {
+            qc.setQueryData<TAlarmSettings>(alarmSettingsKeys.all, resp.result);
+        },
     });
 }

10-10: 쿼리 키 하드코딩 대신 키 상수/키 빌더 사용 권장

['alarmSettings'] 리터럴 대신 키 상수를 사용하면 오타/중복을 줄일 수 있습니다. 위 수정안에서 사용한 alarmSettingsKeys 파일을 신설해 주세요.

추가 파일(신규):

// src/api/settingAlarm/alarm.keys.ts
export const alarmSettingsKeys = {
    all: ['alarmSettings'] as const,
};
src/pages/notice/NoticeDetail.tsx (1)

16-16: 주석이 실제 흐름과 다릅니다. 표현만 정정해 주세요

훅 호출 이전이 아니라, 유효성 검사 결과에 따라 렌더링을 가드하는 흐름입니다. 아래처럼 주석만 바로잡으면 혼동이 줄어듭니다. 코드 로직은 그대로 두고, enabled로 쿼리를 제어하는 현재 방식이 안전합니다.

-    // Id 유효성 검사 -> 훅 호출
+    // Id 유효성 검사 -> 렌더링 가드 (데이터 쿼리는 훅 내부 enabled 옵션으로 제어)
src/types/faq/faq.ts (1)

3-7: FAQ 타입 필드명이 컴포넌트 사용과 일치하지 않습니다

타입 정의의 titlecontent 필드가 Question.tsx에서 사용하는 questionanswer 필드와 일치하지 않아 런타임 오류가 발생할 수 있습니다.

API 응답과 컴포넌트 인터페이스를 일치시켜야 합니다:

export type TFaqItem = {
    faqId: number;
-    title: string;
-    content: string;
+    question: string;
+    answer: string;
};
src/api/faq/faq.ts (1)

5-28: FAQ API 에러 처리 로직 추가 필요

현재 axios 에러가 그대로 전파되는데, FAQ 관련 특별한 에러 처리가 필요한지 검토해보세요.

공통 에러 처리를 추가하면 더 나은 사용자 경험을 제공할 수 있습니다:

+import { AxiosError } from 'axios';
+
// FAQ 목록 조회
export const getFaqs = async (params: { category: TFaqCategory; page: number; size: number }) => {
+    try {
        const { data } = await axiosInstance.get<TFetchFaqsResponse>('/api/v1/faqs', {
            params: {
                faqCategory: params.category,
                page: params.page,
                size: params.size,
            },
        });
        return data;
+    } catch (error) {
+        const axiosError = error as AxiosError;
+        console.error('FAQ 조회 실패:', axiosError.response?.data);
+        throw error;
+    }
};
src/pages/setting/DeleteConfirmPage.tsx (2)

43-47: 에러 타입 처리 개선 필요

any 타입 사용을 피하고 적절한 에러 타입을 사용해야 합니다.

-        onError: (error) => {
-            const msg = error?.response?.data?.message || '회원 탈퇴에 실패했습니다.';
+        onError: (error: unknown) => {
+            const axiosError = error as AxiosError<{ message?: string }>;
+            const msg = axiosError?.response?.data?.message || '회원 탈퇴에 실패했습니다.';
             alert(msg);
         },

120-120: Tailwind CSS 표준 클래스 사용 필요

rounding-32는 Tailwind CSS 표준 클래스가 아닙니다.

-className={`px-8 py-3 rounding-32 font-body1 text-white
+className={`px-8 py-3 rounded-full font-body1 text-white
src/components/settingTab/InfoSetting.tsx (4)

29-33: 빈 문자열 처리 로직 개선 필요

현재 조건문이 truthy 체크라 빈 문자열('')일 때 상태가 갱신되지 않습니다.

 useEffect(() => {
-    if (apiNickname) {
+    if (apiNickname !== undefined && apiNickname !== null) {
         setNickname(apiNickname);
         setInitialNickname(apiNickname);
     }
 }, [apiNickname]);

98-98: pointer-events-none 제거 권장

readOnly 속성만으로도 입력 방지가 충분합니다.

-                className="pointer-events-none"

111-111: Tailwind CSS 표준 클래스 사용 필요

rounding-32는 표준 Tailwind 클래스가 아닙니다.

-className={`bg-primary-500 px-6 py-2 rounding-32 text-white font-body1 ${resetPending ? 'opacity-60 cursor-not-allowed' : ''}`}
+className={`bg-primary-500 px-6 py-2 rounded-full text-white font-body1 ${resetPending ? 'opacity-60 cursor-not-allowed' : ''}`}

14-14: 에러 처리 헬퍼 함수의 타입 개선 필요

any 타입 대신 명시적인 타입을 사용하세요.

+import type { AxiosError } from 'axios';
-const getApiErrorMessage = (err: any, fallback: string) => err?.response?.data?.message ?? (err?.response?.status === 401 ? '로그인이 필요합니다.' : fallback);
+const getApiErrorMessage = (err: unknown, fallback: string) => {
+    const axiosError = err as AxiosError<{ message?: string }>;
+    return axiosError?.response?.data?.message ?? 
+           (axiosError?.response?.status === 401 ? '로그인이 필요합니다.' : fallback);
+};
src/hooks/faq/useFaq.ts (1)

16-16: TypeScript 제네릭 문법 오류 수정 필요

typeof 연산자에는 타입 인수를 전달할 수 없습니다. 옵션 타입을 올바르게 추출해주세요.

-export function useGetFaqs(params: TFaqListParams, options?: Parameters<typeof useCoreQuery<TFetchFaqsResponse>>[2]) {
+export function useGetFaqs(params: TFaqListParams, options?: Parameters<typeof useCoreQuery>[2]) {
-export function useSearchFaqs(params: TFaqSearchParams, options?: Parameters<typeof useCoreQuery<TFetchFaqsResponse>>[2]) {
+export function useSearchFaqs(params: TFaqSearchParams, options?: Parameters<typeof useCoreQuery>[2]) {

Also applies to: 26-26

src/api/notice/notice.ts (2)

5-15: 기존 호출부 호환성을 위한 별칭 export 추가 권장

fetchNotices를 사용하는 기존 코드와의 호환성을 유지하기 위해 임시 별칭을 추가하는 것이 좋습니다.

파일 하단에 다음 코드를 추가해주세요:

// 하위 호환성을 위한 임시 export - 추후 제거 예정
export { getNotices as fetchNotices, getNoticeDetail as fetchNoticeDetail };

36-36: 파일 끝 개행 문자 누락

Prettier 포맷팅 규칙에 따라 파일 끝에 개행을 추가해주세요.

     return data;
-};
+};
+
src/pages/notice/Notice.tsx (1)

61-74: 버튼 타입 명시 완료

type="button" 속성이 올바르게 추가되어 있어 의도하지 않은 폼 제출을 방지합니다.

src/components/common/PasswordEdit.tsx (1)

79-85: 취소 버튼 타입 명시 필요

폼 제출 방지를 위해 type="button" 추가가 필요합니다.

 <button
+    type="button"
     onClick={handleCancel}
     className="px-4 py-1.5 rounding-16 bg-default-gray-400 text-default-gray-700 font-body1 disabled:opacity-60"
     disabled={isPending}
 >
src/hooks/notice/useNotice.ts (1)

28-28: TypeScript 빌드 오류: typeof 연산자에 제네릭 타입 적용 불가

TypeScript에서는 typeof 연산자에 제네릭을 적용할 수 없습니다. 빌드 오류를 해결하려면 제네릭을 제거해야 합니다.

다음과 같이 수정하세요:

-    const useGetNotices = (params: TListParams, options?: Parameters<typeof useCoreQuery<TFetchNoticesResponse>>[2]) => {
+    const useGetNotices = (params: TListParams, options?: Parameters<typeof useCoreQuery>[2]) => {
...
-    const useGetNoticeDetail = (id: number, options?: Parameters<typeof useCoreQuery<TFetchNoticeDetailResponse>>[2]) =>
+    const useGetNoticeDetail = (id: number, options?: Parameters<typeof useCoreQuery>[2]) =>
...
-    const useSearchNotices = (params: TSearchParams, options?: Parameters<typeof useCoreQuery<TFetchNoticesResponse>>[2]) => {
+    const useSearchNotices = (params: TSearchParams, options?: Parameters<typeof useCoreQuery>[2]) => {

Also applies to: 45-45, 52-52

src/pages/question/Question.tsx (4)

37-37: 불필요한 useMemo 의존성 제거

size는 상수값(10)이므로 의존성 배열에 포함할 필요가 없습니다.

         }),
-        [active.value, currentPage],
+        [active.value, currentPage],
     );

// 마찬가지로 paramsSearch에서도:
         }),
-        [searchValue, active.value, currentPage],
+        [searchValue, active.value, currentPage],
     );

Also applies to: 48-48


52-60: placeholderData 중복 설정 제거

useFaq 훅 내부에서 이미 placeholderData: keepPreviousData를 설정하고 있으므로 중복 제거가 필요합니다.

-import { keepPreviousData } from '@tanstack/react-query';
...
     const listQuery = useGetFaqs(paramsList, {
         enabled: !hasKeyword,
-        placeholderData: keepPreviousData,
     });

     const searchQuery = useSearchFaqs(paramsSearch, {
         enabled: hasKeyword,
-        placeholderData: keepPreviousData,
     });

82-82: 빈 함수 핸들러 제거 권장

onSearchClick prop에 빈 함수를 전달하고 있습니다. 필수가 아니라면 제거하는 것이 좋습니다.

-                onSearchClick={() => {}}

또는 검색 시 페이지와 상태를 초기화하는 로직을 구현하세요:

-                onSearchClick={() => {}}
+                onSearchClick={() => {
+                    setCurrentPage(1);
+                    setOpenedIndex(null);
+                }}

90-103: 버튼에 type 속성 추가

버튼 요소에 명시적인 type 속성이 없어 form 내부에서 의도치 않은 submit이 발생할 수 있습니다.

                 {CATEGORY_MAP.map((c) => (
                     <button
+                        type="button"
                         key={c.value}
                         onClick={() => {
src/hooks/auth/useAccount.ts (1)

51-51: resetPreferences 직접 전달 가능

resetPreferences 함수가 파라미터를 받지 않으므로 화살표 함수 래퍼 없이 직접 전달할 수 있습니다.

-        return useCoreMutation<TResetPreferencesResponse, void>(resetPreferences, options);
+        return useCoreMutation<TResetPreferencesResponse, void>(resetPreferences, options);

현재 코드도 동작하지만, 불필요한 래퍼를 제거하면 더 간결해집니다.

src/types/auth/account.ts (1)

28-39: 도메인 값을 유니온 타입으로 제한 권장

타입 안정성 향상을 위해 백엔드 스키마에 맞춰 문자열 리터럴 유니온으로 제한하는 것이 좋습니다.

예시:

export type TMemberInfo = {
    id: number;
    email: string;
    username: string;
    userRank: 'BRONZE' | 'SILVER' | 'GOLD' | 'PLATINUM';
    phoneNumber: string;
    isAuthPayment: boolean;
    gender: 'MALE' | 'FEMALE' | 'UNKNOWN';
    birth: string;
    role: 'USER' | 'ADMIN';
    point: number;
};
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 514d402 and 27301ba.

⛔ Files ignored due to path filters (3)
  • package-lock.json is excluded by !**/package-lock.json
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock, !**/*.lock
📒 Files selected for processing (35)
  • src/api/auth/account.ts (1 hunks)
  • src/api/dates/preferences.ts (1 hunks)
  • src/api/faq/faq.keys.ts (1 hunks)
  • src/api/faq/faq.ts (1 hunks)
  • src/api/notice/notice.keys.ts (1 hunks)
  • src/api/notice/notice.ts (1 hunks)
  • src/api/settingAlarm/alarm.ts (1 hunks)
  • src/components/common/EditableInputBox.tsx (4 hunks)
  • src/components/common/PasswordEdit.tsx (5 hunks)
  • src/components/faq/FAQItem.tsx (1 hunks)
  • src/components/settingTab/AlarmSetting.tsx (1 hunks)
  • src/components/settingTab/InfoSetting.tsx (1 hunks)
  • src/components/settingTab/MembershipSetting.tsx (1 hunks)
  • src/constants/policies.ts (1 hunks)
  • src/hooks/alarm/useDeviceToken.ts (1 hunks)
  • src/hooks/auth/useAccount.ts (1 hunks)
  • src/hooks/customQuery.ts (1 hunks)
  • src/hooks/faq/useFaq.ts (1 hunks)
  • src/hooks/notice/useNotice.ts (1 hunks)
  • src/hooks/settingAlarm/useAlarms.ts (1 hunks)
  • src/pages/home/HomePage.tsx (1 hunks)
  • src/pages/notice/Notice.tsx (1 hunks)
  • src/pages/notice/NoticeDetail.tsx (1 hunks)
  • src/pages/question/Question.tsx (3 hunks)
  • src/pages/setting/DeleteConfirmPage.tsx (4 hunks)
  • src/pages/setting/DeleteReasonPage.tsx.tsx (1 hunks)
  • src/pages/setting/PaymentHistory.tsx (1 hunks)
  • src/pages/setting/SettingEntryPage.tsx (0 hunks)
  • src/routes/routes.tsx (2 hunks)
  • src/types/auth/account.ts (1 hunks)
  • src/types/dates/preferences.ts (1 hunks)
  • src/types/faq/faq.ts (1 hunks)
  • src/types/notice/notice.ts (2 hunks)
  • src/types/settingAlarm/alarm.ts (1 hunks)
  • src/utils/date.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/pages/setting/SettingEntryPage.tsx
🧰 Additional context used
🧬 Code Graph Analysis (31)
src/types/dates/preferences.ts (1)
src/types/common/common.ts (1)
  • TCommonResponse (4-9)
src/utils/date.ts (4)
src/utils/getTodayString.ts (1)
  • getTodayString (1-7)
src/utils/formatDateInput.tsx (1)
  • formatDateInput (1-11)
src/utils/getNextSevenDay.ts (1)
  • getNextSevenDay (1-14)
src/pages/auth/UserSetting.tsx (1)
  • e (109-112)
src/pages/setting/DeleteReasonPage.tsx.tsx (1)
src/pages/setting/SettingEntryPage.tsx (2)
  • requestedTab (14-22)
  • SettingEntryPage (8-30)
src/api/faq/faq.ts (2)
src/types/faq/faq.ts (2)
  • TFaqCategory (17-17)
  • TFetchFaqsResponse (9-15)
src/api/axiosInstance.ts (1)
  • axiosInstance (11-14)
src/api/dates/preferences.ts (2)
src/types/dates/preferences.ts (1)
  • TResetPreferencesResponse (3-3)
src/api/axiosInstance.ts (1)
  • axiosInstance (11-14)
src/api/auth/account.ts (3)
src/types/auth/account.ts (5)
  • TChangePasswordPayload (6-9)
  • TChangeNicknamePayload (11-11)
  • TChangeNicknameResponse (12-17)
  • TMemberInfo (28-39)
  • TGetMemberGradeResponse (50-50)
src/api/axiosInstance.ts (1)
  • axiosInstance (11-14)
src/types/common/common.ts (1)
  • TCommonResponse (4-9)
src/types/faq/faq.ts (1)
src/types/common/common.ts (1)
  • TCommonResponse (4-9)
src/hooks/customQuery.ts (3)
src/types/common/common.ts (1)
  • TUseMutationCustomOptions (17-20)
src/hooks/home/useUserRegion.ts (1)
  • useUserRegion (6-8)
src/hooks/auth/useAuth.ts (1)
  • useAuth (5-14)
src/api/faq/faq.keys.ts (1)
src/queryKey/queryKey.ts (9)
  • () => ['home', 'user', 'grade'] (13-13)
  • () => ['home', 'date-courses', 'saved-count'] (14-14)
  • () => ['home', 'keywords'] (17-17)
  • () => ['home', 'dateTimes'] (18-18)
  • () => ['home', 'user', 'region'] (20-20)
  • () => ['home', 'monthlyPlaceStates'] (19-19)
  • () => ['home'] (12-12)
  • () => ['notice'] (24-24)
  • page (25-25)
src/api/settingAlarm/alarm.ts (3)
src/types/settingAlarm/alarm.ts (3)
  • TGetAlarmSettingsResp (14-14)
  • TAlarmSettings (1-5)
  • TPatchAlarmSettingsResp (15-15)
src/api/axiosInstance.ts (1)
  • axiosInstance (11-14)
src/api/alarm/alarm.ts (2)
  • TRequestGetAlarm (5-13)
  • TRequestPostDeviceToken (15-18)
src/hooks/settingAlarm/useAlarms.ts (3)
src/hooks/customQuery.ts (2)
  • useCoreQuery (7-18)
  • useCoreMutation (21-29)
src/types/settingAlarm/alarm.ts (3)
  • TGetAlarmSettingsResp (14-14)
  • TAlarmSettings (1-5)
  • TPatchAlarmSettingsResp (15-15)
src/api/settingAlarm/alarm.ts (2)
  • getAlarmSettings (6-9)
  • patchAlarmSettings (12-15)
src/pages/setting/PaymentHistory.tsx (1)
src/components/modal/SettingModal.tsx (1)
  • tab (28-37)
src/api/notice/notice.keys.ts (3)
src/queryKey/queryKey.ts (2)
  • () => ['notice'] (24-24)
  • page (25-25)
src/hooks/notices/useGetNotices.ts (1)
  • TRequestGetNoticeRequest (8-15)
src/components/home/info.tsx (1)
  • MainInfo (10-63)
src/routes/routes.tsx (2)
src/pages/setting/PaymentHistory.tsx (1)
  • PaymentHistory (35-95)
src/pages/setting/DeleteConfirmPage.tsx (1)
  • DeleteConfirmPage (26-130)
src/components/settingTab/InfoSetting.tsx (5)
src/hooks/auth/useAccount.ts (2)
  • useAccount (23-55)
  • QUERY_KEYS (18-21)
src/api/auth/account.ts (1)
  • changeNickname (13-16)
src/components/common/EditableInputBox.tsx (1)
  • EditableInputBox (22-160)
src/constants/policies.ts (2)
  • TERMS_URL (1-1)
  • PRIVACY_URL (2-2)
src/pages/TestInputPage.tsx (2)
  • console (41-41)
  • setNickname (40-40)
src/pages/question/Question.tsx (3)
src/types/faq/faq.ts (2)
  • TFaqCategory (17-17)
  • TFaqItem (3-7)
src/hooks/faq/useFaq.ts (2)
  • useGetFaqs (16-23)
  • useSearchFaqs (26-33)
src/components/faq/FAQItem.tsx (1)
  • FAQItem (14-42)
src/hooks/notice/useNotice.ts (5)
src/hooks/customQuery.ts (1)
  • useCoreQuery (7-18)
src/api/notice/notice.keys.ts (1)
  • noticeKeys (1-12)
src/api/notice/notice.ts (3)
  • getNotices (6-15)
  • getNoticeDetail (18-21)
  • searchNotices (24-36)
src/types/notice/notice.ts (1)
  • TFetchNoticeDetailResponse (39-39)
src/components/home/info.tsx (2)
  • MainInfo (10-63)
  • notice (42-57)
src/components/settingTab/AlarmSetting.tsx (1)
src/hooks/settingAlarm/useAlarms.ts (2)
  • useGetAlarmSettings (11-16)
  • usePatchAlarmSettings (19-26)
src/components/common/EditableInputBox.tsx (1)
src/pages/TestInputPage.tsx (3)
  • TestInputPage (8-46)
  • e (39-39)
  • setNickname (40-40)
src/hooks/auth/useAccount.ts (7)
src/types/auth/account.ts (7)
  • TChangePasswordMutationOptions (20-20)
  • TChangePasswordMutationResult (21-21)
  • TChangePasswordPayload (6-9)
  • TChangeNicknameMutationOptions (24-24)
  • TChangeNicknameMutationResult (25-25)
  • TChangeNicknameResponse (12-17)
  • TChangeNicknamePayload (11-11)
src/hooks/customQuery.ts (2)
  • useCoreMutation (21-29)
  • useCoreQuery (7-18)
src/api/auth/account.ts (5)
  • changePassword (7-10)
  • changeNickname (13-16)
  • deleteMember (19-21)
  • getMemberInfo (24-27)
  • getMemberGrade (30-33)
src/types/common/common.ts (1)
  • TUseMutationCustomOptions (17-20)
src/types/dates/preferences.ts (1)
  • TResetPreferencesResponse (3-3)
src/api/dates/preferences.ts (1)
  • resetPreferences (6-9)
src/hooks/home/useDateCourseStates.ts (1)
  • useCoreQuery (7-12)
src/types/notice/notice.ts (2)
src/hooks/notices/useGetNotices.ts (1)
  • TRequestGetNoticeRequest (8-15)
src/components/home/info.tsx (2)
  • MainInfo (10-63)
  • notice (42-57)
src/pages/notice/NoticeDetail.tsx (3)
src/hooks/notice/useNotice.ts (1)
  • useNotice (26-68)
src/utils/date.ts (1)
  • formatDateDot (1-11)
src/components/home/info.tsx (2)
  • MainInfo (10-63)
  • notice (42-57)
src/hooks/faq/useFaq.ts (4)
src/types/faq/faq.ts (2)
  • TFaqCategory (17-17)
  • TFetchFaqsResponse (9-15)
src/hooks/customQuery.ts (1)
  • useCoreQuery (7-18)
src/api/faq/faq.keys.ts (1)
  • faqKeys (1-5)
src/api/faq/faq.ts (2)
  • getFaqs (6-15)
  • searchFaqs (18-28)
src/hooks/alarm/useDeviceToken.ts (1)
src/api/alarm/alarm.ts (1)
  • TRequestPostDeviceToken (15-18)
src/components/common/PasswordEdit.tsx (2)
src/hooks/auth/useAccount.ts (1)
  • useAccount (23-55)
src/pages/auth/FindPw.tsx (4)
  • FindPw (17-207)
  • data (109-116)
  • err (117-119)
  • isValid (101-123)
src/pages/home/HomePage.tsx (1)
src/hooks/home/useUserGrade.ts (2)
  • getUserGrade (8-8)
  • useCoreQuery (7-12)
src/api/notice/notice.ts (3)
src/api/axiosInstance.ts (1)
  • axiosInstance (11-14)
src/types/notice/notice.ts (1)
  • TFetchNoticeDetailResponse (39-39)
src/hooks/notices/useGetNotices.ts (2)
  • TRequestGetNoticeRequest (8-15)
  • page (11-11)
src/pages/notice/Notice.tsx (5)
src/hooks/notice/useNotice.ts (1)
  • useNotice (26-68)
src/types/notice/notice.ts (1)
  • TNoticeItem (4-9)
src/components/common/EditableInputBox.tsx (1)
  • EditableInputBox (22-160)
src/utils/date.ts (1)
  • formatDateDot (1-11)
src/components/common/navigator.tsx (1)
  • Navigator (10-43)
src/pages/setting/DeleteConfirmPage.tsx (2)
src/hooks/auth/useAccount.ts (1)
  • useAccount (23-55)
src/pages/auth/UserSetting.tsx (1)
  • User (23-178)
src/components/settingTab/MembershipSetting.tsx (2)
src/hooks/auth/useAccount.ts (1)
  • useAccount (23-55)
src/components/common/ToggleSwitch.tsx (1)
  • ToggleSwitch (11-48)
src/types/auth/account.ts (1)
src/types/common/common.ts (2)
  • TUseMutationCustomOptions (17-20)
  • TCommonResponse (4-9)
🪛 Biome (2.1.2)
src/types/notice/notice.ts

[error] 11-12: Expected a statement but instead found '<<<<<<< HEAD'.

Expected a statement here.

(parse)

src/components/faq/FAQItem.tsx

[error] 32-32: The elements with this role can be changed to the following elements:

For examples and more information, see WAI-ARIA Roles

(lint/a11y/useSemanticElements)

src/components/common/PasswordEdit.tsx

[error] 82-93: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🪛 GitHub Check: Build
src/types/notice/notice.ts

[failure] 20-20:
Merge conflict marker encountered.


[failure] 18-18:
Merge conflict marker encountered.


[failure] 11-11:
Merge conflict marker encountered.

🪛 GitHub Actions: CI
src/types/notice/notice.ts

[error] 11-11: ESLint: Parsing error: Merge conflict marker encountered. Step: pnpm lint.

🔇 Additional comments (16)
src/utils/date.ts (1)

1-11: LGTM: 유효성 검사 + 타임존 명시로 안정적 포맷팅

빈 문자열/Invalid Date 가드와 Asia/Seoul 타임존 명시가 적절합니다. 다른 유틸과도 일관적입니다.

src/types/settingAlarm/alarm.ts (1)

1-5: 알람 설정 타입 정의 깔끔합니다

서버 응답과 UI 상태를 연결하기에 충분히 명확합니다.

src/components/faq/FAQItem.tsx (1)

15-16: contentId를 faqId 기반으로 생성한 점 좋습니다

DOM id 유효성/중복 위험을 줄였습니다.

src/types/dates/preferences.ts (1)

3-3: 타입 정의가 적절합니다

TResetPreferencesResponse 타입이 빈 결과를 반환하는 API 응답을 명확하게 표현하고 있습니다. Record<string, never>를 사용하여 빈 객체를 타입 안전하게 정의한 점이 좋습니다.

src/api/dates/preferences.ts (1)

5-9: API 함수 구현이 깔끔합니다

DELETE 메서드를 사용한 취향 데이터 초기화 API가 올바르게 구현되었습니다. 타입 정의와 일관성 있게 작성되었습니다.

src/routes/routes.tsx (1)

140-145: ProtectedRoute 적용이 적절합니다

결제 내역과 계정 삭제 관련 페이지들에 ProtectedRoute를 적용하여 인증이 필요한 페이지들을 보호한 것이 좋습니다. 일관성 있는 보안 처리입니다.

Also applies to: 148-153, 156-161

src/hooks/customQuery.ts (1)

20-21: 제네릭 타입 개선이 좋습니다

useCoreMutation 함수의 options 파라미터를 제네릭 타입 TUseMutationCustomOptions<T, U>로 변경한 것이 적절합니다. 이제 mutation의 반환 타입과 변수 타입이 정확하게 전달됩니다.

src/api/settingAlarm/alarm.ts (1)

7-7: withCredentials 중복 지정이 없는 점 좋습니다

공통 axios 인스턴스에 withCredentials: true가 이미 설정되어 있고, 개별 요청에서 중복 지정이 없어 일관성이 좋습니다.

Also applies to: 13-13

src/components/settingTab/MembershipSetting.tsx (1)

15-15: 등급 텍스트 로직이 정확합니다

로딩, 에러, 데이터 없음 상태를 적절히 처리하고 있습니다.

src/api/auth/account.ts (1)

1-33: 잘 구현되었습니다!

계정 관련 API 함수들이 명확하고 일관된 패턴으로 구현되었습니다. 타입 정의와 API 엔드포인트 매핑이 적절합니다.

src/api/notice/notice.ts (1)

32-32: 조건부 파라미터 처리 개선

더 간결한 방식으로 개선 가능합니다.

-            ...(category && { noticeCategory: category }),
+            ...(category && { noticeCategory: category }),

현재 코드도 정상 동작하지만, 빈 객체 생성을 피하려면 다음과 같이 개선할 수 있습니다:

-            ...(category && { noticeCategory: category }),
+            ...(category ? { noticeCategory: category } : {}),

실제로는 현재 구현이 더 간결하므로 그대로 유지해도 좋습니다.

src/pages/notice/Notice.tsx (2)

32-36: React Hook 규칙 준수 확인 필요

현재 구현은 enabled 옵션을 통해 조건부 실행을 제어하므로 Hook 규칙을 위반하지 않습니다. 두 훅을 모두 호출하고 enabled로 제어하는 방식은 올바른 패턴입니다.


88-88: 날짜 포맷 통일 완료

formatDateDot 유틸리티를 사용하여 날짜 형식이 일관되게 표시됩니다. 좋은 개선입니다.

src/components/common/PasswordEdit.tsx (2)

31-40: 비밀번호 변경 API 연동 잘 구현됨

useChangePassword 훅을 활용한 서버 연동이 적절히 구현되었습니다. 성공/실패 처리와 사용자 피드백이 명확합니다.


172-179: 저장 버튼 타입이 올바르게 명시됨

type="button" 속성이 적절히 추가되어 있고, 로딩 상태 표시도 잘 구현되었습니다.

src/hooks/auth/useAccount.ts (1)

18-21: 쿼리 키 상수화 구현 확인

쿼리 키를 상수로 관리하는 좋은 패턴을 적용하셨습니다. 유지보수성이 향상되었습니다.

Comment on lines +1 to +5
export const faqKeys = {
all: ['faqs'] as const,
list: (p: { category: string; page: number; size: number }) => [...faqKeys.all, 'list', p] as const,
search: (p: { keyword: string; category?: string; page: number; size: number }) => [...faqKeys.all, 'search', p] as const,
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

키 빌더 패턴 적절합니다. 파라미터 타입 재사용을 검토해 주세요

구조/리터럴 타입 보존(as const)도 적절합니다. 다만 list, search의 파라미터 구조가 다른 곳에서도 쓰인다면 공용 타입(예: TFaqListParams, TFaqSearchParams)으로 분리/재사용하면 추후 변경 시 추적이 용이합니다.

🤖 Prompt for AI Agents
In src/api/faq/faq.keys.ts around lines 1 to 5, the inline parameter object
types for list and search should be extracted into shared TypeScript types for
reuse and maintainability; create exported types (e.g. TFaqListParams and
TFaqSearchParams) that capture the existing shapes (list: { category: string;
page: number; size: number }, search: { keyword: string; category?: string;
page: number; size: number }), replace the inline annotations in faqKeys.list
and faqKeys.search with these new types, and export the types so other modules
can import and reuse them.

Comment on lines +1 to +12
export const noticeKeys = {
root: ['notice'] as const, // 루트 키

// 목록 조회 키
list: (p: { category: 'SERVICE' | 'SYSTEM'; page: number; size: number }) => [...noticeKeys.root, 'list', p] as const,

// 상세 조회 키
detail: (id: number) => [...noticeKeys.root, 'detail', id] as const,

// 검색 조회 키
search: (p: { keyword: string; page: number; size: number; category?: 'SERVICE' | 'SYSTEM' }) => [...noticeKeys.root, 'search', p] as const,
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Notice 키 정의가 두 곳으로 분산되어 있습니다 — 단일 소스로 통합 권장

relevant_code_snippets에 따르면 기존에도 NoticeKeys(getAllNotices 등)가 존재합니다. 키 스키마가 중복되면 캐시 충돌/이중 관리가 생깁니다. notice.keys.ts를 단일 소스로 승격하고 기존 키는 deprecated 처리 또는 래핑으로 일원화해 주세요.

레거시 키 사용처를 빠르게 확인하려면:


🏁 Script executed:

#!/bin/bash
# 레거시 NoticeKeys 사용처 탐색
rg -n -A 2 "NoticeKeys\."
# 신규 noticeKeys 사용처 탐색
rg -n -A 2 "noticeKeys\."

Length of output: 1895


🏁 Script executed:

#!/bin/bash
# 레거시 NoticeKeys 정의 위치 탐색
rg -n "export const NoticeKeys" -C 2
rg -n "NoticeKeys\s*=\s*{" -C 2

Length of output: 527


🏁 Script executed:

#!/bin/bash
# NoticeKeys 전체 정의 블록 확인
rg -n "export const NoticeKeys" -A 20 src/queryKey/queryKey.ts

Length of output: 290


Notice 키 정의 단일화 필요
현재 noticeKeys(src/api/notice/notice.keys.ts)와 레거시 NoticeKeys(src/queryKey/queryKey.ts)에 키가 중복되어 관리되고 있습니다. 이로 인해 캐시 충돌이나 이중 관리 리스크가 발생할 수 있습니다. 아래 파일을 하나의 소스로 통합하고, 레거시 정의는 deprecated 처리 또는 새 정의로 래핑해 주세요.

영향 받는 위치:

  • src/queryKey/queryKey.ts
    export const NoticeKeys = createQueryKeys('notice', { … })
  • src/api/notice/notice.keys.ts
    export const noticeKeys = { root, list, detail, search }
  • src/hooks/notices/useGetNotices.ts
    NoticeKeys.getAllNotices(...) 사용
  • src/hooks/notice/useNotice.ts
    noticeKeys.list(...), noticeKeys.detail(...), noticeKeys.search(...) 사용

예시(diff 제안):

// src/queryKey/queryKey.ts
-export const NoticeKeys = createQueryKeys('notice', {
-  all: () => ['notice'],
-  getAllNotices: (page, size, noticeCategory) => ['notice', page, size, noticeCategory],
-});
+export const noticeKeys = createQueryKeys('notice', {
+  root: () => ['notice'],
+  list: (p: { category: 'SERVICE' | 'SYSTEM'; page: number; size: number }) =>
+    [...root(), 'list', p] as const,
+  detail: (id: number) =>
+    [...root(), 'detail', id] as const,
+  search: (p: { keyword: string; page: number; size: number; category?: 'SERVICE' | 'SYSTEM' }) =>
+    [...root(), 'search', p] as const,
+});
  • 기존 NoticeKeys.getAllNotices 사용부는 noticeKeys.list로 교체
  • 레거시 키에는 /** @deprecated use noticeKeys.list */ 주석 추가

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/api/notice/notice.keys.ts (lines 1-12) and the legacy
src/queryKey/queryKey.ts, unify the notice key definitions by making
src/api/notice/notice.keys.ts the single source of truth: export noticeKeys as
the canonical object and change the legacy NoticeKeys export to be a thin
wrapper that delegates to noticeKeys (e.g., map NoticeKeys.getAllNotices →
noticeKeys.list, getNotice → noticeKeys.detail, search → noticeKeys.search) with
a /** @deprecated use noticeKeys.* */ comment on each legacy member; then update
usages (src/hooks/notices/useGetNotices.ts and src/hooks/notice/useNotice.ts) to
import and use noticeKeys.list/detail/search respectively, remove duplicate
logic from the legacy file or leave it delegating to noticeKeys, and ensure
typings/signatures match so no callsites need edits beyond import replacement.

Comment on lines +94 to +102
<textarea
ref={inputRef as React.RefObject<HTMLTextAreaElement>}
value={value}
onChange={onChange}
placeholder={placeholder}
maxLength={maxLength}
className={`${sharedClassName} resize-none`}
onFocus={onFocus}
/>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

readOnly=true일 때 닉네임 편집이 막히지 않습니다

textarea에는 readOnly가 전달되지 않아 편집이 가능해집니다. 읽기 전용 모드에서 편집 진입 자체도 차단하는 것이 일관됩니다.

                 {isNickname && isEditing ? (
                     <textarea
                         ref={inputRef as React.RefObject<HTMLTextAreaElement>}
                         value={value}
                         onChange={onChange}
                         placeholder={placeholder}
                         maxLength={maxLength}
+                        readOnly={readOnly}
                         className={`${sharedClassName} resize-none`}
                         onFocus={onFocus}
                     />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<textarea
ref={inputRef as React.RefObject<HTMLTextAreaElement>}
value={value}
onChange={onChange}
placeholder={placeholder}
maxLength={maxLength}
className={`${sharedClassName} resize-none`}
onFocus={onFocus}
/>
<textarea
ref={inputRef as React.RefObject<HTMLTextAreaElement>}
value={value}
onChange={onChange}
placeholder={placeholder}
maxLength={maxLength}
readOnly={readOnly}
className={`${sharedClassName} resize-none`}
onFocus={onFocus}
/>
🤖 Prompt for AI Agents
In src/components/common/EditableInputBox.tsx around lines 94-102, the textarea
is missing the readOnly prop so the field remains editable even when
readOnly=true; add readOnly={readOnly} and aria-readonly={readOnly} to the
textarea, and prevent entering edit mode by not calling the onFocus handler when
readOnly is true (pass undefined or a conditional wrapper), ensuring onChange
cannot mutate value in read-only mode.

Comment on lines +118 to 126
{isNickname && !isEditing && (
<button
onClick={() => setIsEditing(true)}
type="button"
onClick={() => {
setIsEditing(true);
onEditStart?.();
}}
className="absolute right-3 top-1/2 -translate-y-1/2 font-body1 px-3 py-1 rounded-full bg-default-gray-400 text-default-gray-700"
>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

읽기 전용 모드에서 '수정' 버튼 비활성화/가드 추가

readOnly=true에서도 수정 버튼이 동작합니다. 비활성화와 클릭 가드를 함께 적용해 주세요.

                 {isNickname && !isEditing && (
                     <button
-                        type="button"
-                        onClick={() => {
-                            setIsEditing(true);
-                            onEditStart?.();
-                        }}
+                        type="button"
+                        disabled={readOnly}
+                        onClick={() => {
+                            if (readOnly) return;
+                            setIsEditing(true);
+                            onEditStart?.();
+                        }}
                         className="absolute right-3 top-1/2 -translate-y-1/2 font-body1 px-3 py-1 rounded-full bg-default-gray-400 text-default-gray-700"
                     >
                         수정
                     </button>
                 )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{/* 수정 */}
{isNickname && !isEditing && (
<button
onClick={() => setIsEditing(true)}
type="button"
onClick={() => {
setIsEditing(true);
onEditStart?.();
}}
className="absolute right-3 top-1/2 -translate-y-1/2 font-body1 px-3 py-1 rounded-full bg-default-gray-400 text-default-gray-700"
{/* 수정 */}
{isNickname && !isEditing && (
<button
type="button"
disabled={readOnly}
onClick={() => {
if (readOnly) return;
setIsEditing(true);
onEditStart?.();
}}
className="absolute right-3 top-1/2 -translate-y-1/2 font-body1 px-3 py-1 rounded-full bg-default-gray-400 text-default-gray-700"
>
수정
</button>
)}
🤖 Prompt for AI Agents
In src/components/common/EditableInputBox.tsx around lines 118 to 126, the "수정"
button is still clickable when readOnly=true; update the button to respect
readOnly by adding the disabled attribute (disabled={readOnly}) and
aria-disabled for accessibility, and guard the onClick handler so it returns
early if readOnly is true (preventing setIsEditing and onEditStart from
running). Ensure styling reflects disabled state if needed and keep existing
className unchanged except to add any disabled styles.

Comment on lines 140 to 144
<button type="button" onClick={onSearchClick} className="absolute right-3 top-1/2 -translate-y-1/2 p-1">
<SearchIcon className="w-5 h-5 text-primary-500" stroke="currentColor" />
</button>
)}
</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

검색 버튼에 접근성 라벨 추가 권장

아이콘 버튼은 스크린리더에 의미가 전달되지 않습니다. aria-label을 추가해 주세요.

-                {isSearch && (
-                    <button type="button" onClick={onSearchClick} className="absolute right-3 top-1/2 -translate-y-1/2 p-1">
+                {isSearch && (
+                    <button type="button" aria-label="검색 실행" onClick={onSearchClick} className="absolute right-3 top-1/2 -translate-y-1/2 p-1">
                         <SearchIcon className="w-5 h-5 text-primary-500" stroke="currentColor" />
                     </button>
                 )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{isSearch && (
<button type="button" onClick={onSearchClick} className="absolute right-3 top-1/2 -translate-y-1/2 p-1">
<SearchIcon className="w-5 h-5 text-primary-500" stroke="currentColor" />
</button>
)}
{isSearch && (
<button
type="button"
aria-label="검색 실행"
onClick={onSearchClick}
className="absolute right-3 top-1/2 -translate-y-1/2 p-1"
>
<SearchIcon
className="w-5 h-5 text-primary-500"
stroke="currentColor"
/>
</button>
)}
🤖 Prompt for AI Agents
In src/components/common/EditableInputBox.tsx around lines 140 to 144, the
search icon button lacks an accessible name for screen readers; add an
aria-label (e.g., aria-label="Search") or aria-labelledby that provides a clear,
localizable description, or pass the label via props if needed, keeping the
existing button type and classes so the icon remains visually unchanged but is
announced by assistive technologies.

Comment on lines +1 to +2
export const TERMS_URL = 'https://continuous-headphones-f4c.notion.site/1ece4447020b8049a727d11c3f853a46?source=copy_link';
export const PRIVACY_URL = 'https://www.notion.so/1ece4447020b80c8befcd2f3886a0350?source=copy_link';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

정책 URL은 환경 분리 고려, 불필요 쿼리 제거 제안

  • 운영/스테이징에서 URL이 달라질 수 있어 .env 또는 별도 설정에서 주입하는 방식을 고려해 주세요.
  • Notion 공유 링크의 ?source=copy_link는 불필요하므로 제거하면 깔끔합니다.

불필요 쿼리 제거 예시:

-export const TERMS_URL = 'https://continuous-headphones-f4c.notion.site/1ece4447020b8049a727d11c3f853a46?source=copy_link';
-export const PRIVACY_URL = 'https://www.notion.so/1ece4447020b80c8befcd2f3886a0350?source=copy_link';
+export const TERMS_URL = 'https://continuous-headphones-f4c.notion.site/1ece4447020b8049a727d11c3f853a46';
+export const PRIVACY_URL = 'https://www.notion.so/1ece4447020b80c8befcd2f3886a0350';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const TERMS_URL = 'https://continuous-headphones-f4c.notion.site/1ece4447020b8049a727d11c3f853a46?source=copy_link';
export const PRIVACY_URL = 'https://www.notion.so/1ece4447020b80c8befcd2f3886a0350?source=copy_link';
export const TERMS_URL = 'https://continuous-headphones-f4c.notion.site/1ece4447020b8049a727d11c3f853a46';
export const PRIVACY_URL = 'https://www.notion.so/1ece4447020b80c8befcd2f3886a0350';
🤖 Prompt for AI Agents
In src/constants/policies.ts around lines 1 to 2, the hardcoded Notion policy
URLs include environment-dependent values and unnecessary query strings; change
these to be injected from environment (e.g., process.env or a config module) so
staging/production can override them, and remove the `?source=copy_link` query
from the stored URLs so the constants hold clean canonical links.

<div className="max-w-[960px] mx-auto font-body1 text-default-gray-800">
{/* 뒤로가기 버튼 */}
<button onClick={() => navigate('/setting', { state: { openSettingTab: '멤버십' } })}>
<button onClick={() => navigate('/home')}>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

뒤로가기 UX 개선: 히스토리 우선, 폴백은 홈

직접 /home으로 고정 이동하면 외부 진입/내부 네비게이션 구분이 사라집니다. 브라우저 히스토리가 있을 땐 뒤로가기, 없으면 홈으로 이동하는 패턴을 권장합니다.

-                <button onClick={() => navigate('/home')}>
+                <button onClick={() => (window.history.length > 1 ? navigate(-1) : navigate('/home'))}>
🤖 Prompt for AI Agents
In src/pages/setting/PaymentHistory.tsx around line 46, the back button
currently always navigates to '/home'; change it to prefer going back in browser
history and only fall back to home when there is no prior history: replace the
onClick handler to check window.history.length (or similar) and call
navigate(-1) when history exists, otherwise call navigate('/home'); ensure this
runs client-side and works with React Router's navigate function.

Comment on lines +7 to +12
export type TApiEnvelope<T> = {
isSuccess: boolean;
code: string;
message: string;
result: T;
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

ApiEnvelope는 공용 타입으로 분리하는 것을 권장합니다

TApiEnvelope는 FAQ/공지/계정 등 전역적으로 재사용될 가능성이 큽니다. src/types/common/api.ts 같은 공용 위치로 이동해 단일 소스로 관리해 주세요. 추후 스펙 변경(예: code가 number로 변동, result 누락 등)에 대응이 쉬워집니다.

🤖 Prompt for AI Agents
In src/types/settingAlarm/alarm.ts around lines 7 to 12, the TApiEnvelope
generic is a shared API contract and should be moved to a common location;
create a new file src/types/common/api.ts exporting the TApiEnvelope type,
update this file and any other files that reference TApiEnvelope to import it
from 'src/types/common/api', and remove the duplicate definition from
src/types/settingAlarm/alarm.ts; ensure the new export uses the same type
signature and run a project-wide import/refactor to fix any broken imports or
lint/type errors.

💡 Verification agent

🧩 Analysis chain

백엔드 스펙(특히 result 필드 필수 여부) 확인 필요

일부 API가 실패 시 result를 생략하거나 204를 반환한다면, result?: T | null 형태가 필요할 수 있습니다. Swagger/백엔드 계약 확인을 부탁드립니다.

다음 스크립트로 유사한 Envelope 타입 중복 선언 여부도 함께 점검해 주세요.


🏁 Script executed:

#!/bin/bash
# ApiEnvelope/Envelope 류 타입 중복 정의 검색
rg -n -A 2 -e "type\s+(T)?ApiEnvelope" -e "interface\s+(T)?ApiEnvelope" -e "isSuccess:\s*boolean;" -e "result:\s*"

Length of output: 2068


#!/bin/bash
echo "===== src/types/common/common.ts (1-20) ====="
sed -n '1,20p' src/types/common/common.ts
echo
echo "===== src/api/axiosInstance.ts (1-20) ====="
sed -n '1,20p' src/api/axiosInstance.ts

공통 ApiEnvelope 타입 재사용 및 result 필드 옵션화 확인 필요

아래 파일들에서 동일한 형태의 Envelope 응답 타입이 중복 선언되고 있습니다.

  1. src/types/common/common.ts
  2. src/types/settingAlarm/alarm.ts
  3. src/types/auth/auth.ts
  4. src/types/auth/account.ts
  5. src/api/axiosInstance.ts

제안

  • src/types/common/common.ts에 정의된 공통 Envelope 타입을 재사용하여 중복 제거
  • 백엔드 Swagger/계약서 확인 후, 실패 응답(204 또는 result 미포함) 케이스가 있다면 result?: T | null 처럼 옵셔널/nullable 처리

위 두 가지를 반영해 주세요.

Comment on lines +14 to +15
export type TGetAlarmSettingsResp = TApiEnvelope<TAlarmSettings>;
export type TPatchAlarmSettingsResp = TApiEnvelope<TAlarmSettings>;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

PATCH 요청 페이로드는 Partial로 여지를 두는 편이 안전합니다

알림 설정 일부만 갱신하는 API라면, 요청 페이로드를 Partial로 두면 확장성과 불변성이 올라갑니다. 응답 타입은 현행 유지 가능합니다.

아래처럼 타입을 추가해 두고 API 계층에서 활용해 보세요.

 export type TGetAlarmSettingsResp = TApiEnvelope<TAlarmSettings>;
 export type TPatchAlarmSettingsResp = TApiEnvelope<TAlarmSettings>;
+export type TPatchAlarmSettingsPayload = Partial<TAlarmSettings>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export type TGetAlarmSettingsResp = TApiEnvelope<TAlarmSettings>;
export type TPatchAlarmSettingsResp = TApiEnvelope<TAlarmSettings>;
export type TGetAlarmSettingsResp = TApiEnvelope<TAlarmSettings>;
export type TPatchAlarmSettingsResp = TApiEnvelope<TAlarmSettings>;
export type TPatchAlarmSettingsPayload = Partial<TAlarmSettings>;
🤖 Prompt for AI Agents
In src/types/settingAlarm/alarm.ts around lines 14-15, the PATCH request payload
types are currently full TAlarmSettings; change this to accept partial updates
by adding a new request type using Partial<TAlarmSettings> (e.g.,
TPatchAlarmSettingsReq) and use that in the API layer while leaving the response
types unchanged — update exports and any API interfaces that send PATCH payloads
to reference the new Partial-based request type.

@Seojegyeong Seojegyeong changed the title feat: 설정API 연동 미완성 feat: 설정API 연동 완성 Aug 13, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🔭 Outside diff range comments (2)
src/types/notice/notice.ts (1)

11-18: 페이지 응답 메타를 공통 타입으로 분리하면 재사용성과 일관성이 높아집니다

여러 리스트 API에서 동일한 메타 필드(totalPages/currentPage/currentSize/hasNextPage)를 사용할 가능성이 높습니다. 공통 타입으로 분리해 두면 유지보수가 쉬워집니다. 또한 currentSize는 pageSize가 더 직관적입니다. 검토 부탁드립니다.

아래처럼 메타 타입을 분리하고 교차 타입으로 합치는 방식을 제안드립니다.

-export type TFetchNoticesResponse = TCommonResponse<{
-    noticeList: TNoticeItem[];
-    totalPages: number;
-    currentPage: number;
-    currentSize: number;
-    hasNextPage: boolean;
-}>;
+export type TFetchNoticesResponse = TCommonResponse<
+    {
+        noticeList: TNoticeItem[];
+    } & TPaginationMeta
+>;

메타 타입 추가(파일 내 적절한 상단 위치에 선언):

export type TPaginationMeta = {
    totalPages: number;
    currentPage: number; // 0-based인지 1-based인지 주석으로 명확화 권장
    pageSize: number; // 기존 currentSize → pageSize로 권장
    hasNextPage: boolean;
};
src/components/home/info.tsx (1)

49-58: button의 onKeyDown 핸들러는 중복입니다 — 제거 또는 Link 사용 권장

button 요소는 기본적으로 Enter/Space 키로 클릭 이벤트가 발생합니다. onKeyDown으로 동일 동작을 중복 처리할 필요가 없습니다. 더 나아가 시맨틱하게는 Link 사용이 적합합니다.

중복 제거(간단안):

-                                <button
-                                    className="whitespace-nowrap text-ellipsis overflow-hidden w-full text-left"
-                                    onClick={() => navigate(`/notice/${notice.noticeId}`)}
-                                    onKeyDown={(e) => {
-                                        if (e.key === 'Enter' || e.key === ' ') {
-                                            navigate(`/notice/${notice.noticeId}`);
-                                        }
-                                    }}
-                                    tabIndex={0}
-                                >
+                                <button
+                                    className="whitespace-nowrap text-ellipsis overflow-hidden w-full text-left"
+                                    onClick={() => navigate(`/notice/${notice.noticeId}`)}
+                                >

또는 Link 사용(권장안):

// 상단에 추가: import { Link } from 'react-router-dom';

<li key={notice.noticeId}>
  <Link
    to={`/notice/${notice.noticeId}`}
    className="whitespace-nowrap text-ellipsis overflow-hidden w-full text-left inline-block"
  >
    {notice.title}
  </Link>
</li>
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27301ba and 7d44260.

📒 Files selected for processing (3)
  • src/components/home/info.tsx (1 hunks)
  • src/hooks/notices/useGetNotices.ts (0 hunks)
  • src/types/notice/notice.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • src/hooks/notices/useGetNotices.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/types/notice/notice.ts (4)
src/pages/notice/Notice.tsx (4)
  • Notice (13-119)
  • getNotices (29-55)
  • notice (97-104)
  • setLoading (30-52)
src/api/notice/notice.ts (2)
  • TRequestGetNoticeRequest (6-11)
  • noticeId (14-17)
src/pages/notice/NoticeDetail.tsx (2)
  • NoticeDetail (8-69)
  • noticeId (17-42)
src/hooks/notices/useGetNotices.ts (1)
  • TRequestGetNoticeRequest (8-15)
src/components/home/info.tsx (3)
src/hooks/notice/useNotice.ts (1)
  • useNotice (26-68)
src/pages/notice/Notice.tsx (4)
  • Notice (13-119)
  • getNotices (29-55)
  • category (76-89)
  • notice (97-104)
src/hooks/notices/useGetNotices.ts (2)
  • page (11-11)
  • TRequestGetNoticeRequest (8-15)
🔇 Additional comments (4)
src/types/notice/notice.ts (2)

11-18: TRequestGetNoticeRequest 타입 제거: 참조 없음 확인
전역 검색 결과 TRequestGetNoticeRequest가 정의되거나 사용되는 부분이 전혀 없어 안전하게 제거된 것으로 확인되었습니다. 추가 검토나 조치가 필요하지 않습니다.


29-30: null 반환 규약 일관 확인됨

  • src/types/notice/notice.ts:30을 비롯해 모든 TFetch*DetailResponse 타입에서 null만 사용 중이며 undefined는 발견되지 않았습니다.
  • 따라서 현재 방식이 전역 규약과 일치합니다.

선택 사항으로, 404 응답 시 null 반환임을 명시하는 주석을 덧붙이면 사용처 이해에 도움이 될 수 있습니다.

src/components/home/info.tsx (2)

28-29: 데이터 경로 업데이트 정합성 확인됨

data?.result?.noticeList ?? []로 경로가 래퍼/신규 응답 타입과 일치합니다. NPE 안전 처리도 적절합니다.


4-4: 레거시 훅 및 타입 참조 미발견 확인
전체 코드베이스를 점검한 결과, useGetNotices.ts 파일과 @/hooks/notices/useGetNotices 임포트, TRequestGetNoticeRequest 타입 사용이 전혀 발견되지 않았습니다. 빌드·런타임 혼선 우려가 없으므로 이 이슈는 해결되었습니다.

Comment on lines +12 to +15
const { useGetNotices } = useNotice();

if (error) {
return <Navigate to={'/error'} replace />;
const { data, isError, isLoading } = useGetNotices({ size: 3, page: 0, category: 'SERVICE' });

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

새 래퍼(useNotice)로의 마이그레이션 적절합니다

useCoreQuery 래퍼로 일원화해 반환 형태를 단순화하신 점 좋습니다. 파라미터 객체는 내부에서 안정화(useMemo) 처리되고 있어 성능 측면도 문제 없습니다.

추가로, 카테고리 리터럴 'SERVICE'는 오타에 취약하므로 상수/유니온 타입으로 관리하면 안전합니다.

-const { data, isError, isLoading } = useGetNotices({ size: 3, page: 0, category: 'SERVICE' });
+const CATEGORY = 'SERVICE' as const; // 혹은 enum/리터럴 유니온으로 관리
+const { data, isError, isLoading } = useGetNotices({ size: 3, page: 0, category: CATEGORY });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { useGetNotices } = useNotice();
if (error) {
return <Navigate to={'/error'} replace />;
const { data, isError, isLoading } = useGetNotices({ size: 3, page: 0, category: 'SERVICE' });
const { useGetNotices } = useNotice();
const CATEGORY = 'SERVICE' as const; // 혹은 enum/리터럴 유니온으로 관리
const { data, isError, isLoading } = useGetNotices({ size: 3, page: 0, category: CATEGORY });
🤖 Prompt for AI Agents
In src/components/home/info.tsx around lines 12 to 15, the category argument is
a string literal 'SERVICE' which is error-prone; replace it with a typed
constant or union type (e.g., NOTICE_CATEGORY.SERVICE or a NoticeCategory union)
imported from the central constants/types file, update the call to
useGetNotices({ size: 3, page: 0, category: NOTICE_CATEGORY.SERVICE }), and
ensure the useGetNotices parameter typing accepts that enum/union so the
compiler enforces valid categories.

Comment on lines +16 to 18
if (isError) {
return <Navigate to="/error" replace />;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

에러 시 전역 리다이렉션 대신 컴포넌트 내 폴백을 권장

홈 주요 카드에서 네비게이션 리다이렉트는 UX 점프가 커 보입니다. 카드 내부에 간단한 에러 메시지를 표시하고, ‘새로고침’이나 ‘다시 시도’ 버튼을 두는 방향을 권장드립니다.

-    if (isError) {
-        return <Navigate to="/error" replace />;
-    }
+    if (isError) {
+        return (
+            <MainCard>
+                <div className="text-center text-red-500 py-8">공지사항을 불러오지 못했습니다. 잠시 후 다시 시도해 주세요.</div>
+            </MainCard>
+        );
+    }

Comment on lines 20 to 26
if (isLoading) {
return (
<MainCard>
<ClipLoader className="self-center" />
</MainCard>
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

로딩 스피너 접근성 보강 제안

스크린 리더 인식 개선을 위해 로딩 영역에 role/aria 속성 또는 시각 숨김 텍스트를 추가해 주세요.

예시:

-            <MainCard>
-                <ClipLoader className="self-center" />
-            </MainCard>
+            <MainCard>
+                <div role="status" aria-live="polite" className="self-center">
+                    <ClipLoader className="self-center" />
+                    <span className="sr-only">로딩 중…</span>
+                </div>
+            </MainCard>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (isLoading) {
return (
<MainCard>
<ClipLoader className="self-center" />
</MainCard>
);
}
if (isLoading) {
return (
<MainCard>
<div role="status" aria-live="polite" className="self-center">
<ClipLoader className="self-center" />
<span className="sr-only">로딩 중…</span>
</div>
</MainCard>
);
}
🤖 Prompt for AI Agents
In src/components/home/info.tsx around lines 20 to 26, the loading spinner lacks
accessibility attributes and hidden text for screen readers; wrap the spinner in
an accessible container (e.g., a div) with role="status", aria-live="polite" and
aria-busy="true", and include visually hidden text like "Loading content…"
(using your existing .sr-only utility or an inline style) so screen readers
announce the loading state while keeping the spinner visually unchanged.

Comment on lines +3 to 9
// 공지 항목
export type TNoticeItem = {
noticeId: number;
title: string;
isPinned: boolean;
createdAt: string;
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

중복 필드 분리로 타입 중복 최소화 제안

TNoticeItemTNoticeDetail이 공통 필드(noticeId/title/isPinned/createdAt)를 공유합니다. 베이스 타입을 두고 확장하면 중복이 줄고 일관성이 좋아집니다.

예시:

type TNoticeBase = {
    noticeId: number;
    title: string;
    isPinned: boolean;
    createdAt: string; // ISO 8601 문자열인지 주석으로 명시 권장
};

export type TNoticeItem = TNoticeBase;

export type TNoticeDetail = TNoticeBase & {
    content: string;
};
🤖 Prompt for AI Agents
In src/types/notice/notice.ts around lines 3 to 9, TNoticeItem duplicates fields
that are also used by TNoticeDetail; extract a shared base type (e.g.,
TNoticeBase) containing noticeId, title, isPinned, createdAt and then redefine
TNoticeItem as that base and TNoticeDetail as the base extended with content,
optionally annotating createdAt as an ISO 8601 string for clarity.

@yeonjin719
Copy link
Member

AI가 남기는 코멘트 전부 해결 안해도 됩니다! 거르면서 작업하세용!

@yeonjin719
Copy link
Member

완성하셨다고 판단되시면 bypass 머지해주세여

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🔭 Outside diff range comments (7)
src/components/common/ToggleSwitch.tsx (6)

27-31: 접근성(a11y) 보완: switch role/aria-checked/type="button"/disabled 스타일 추가 권장

토글은 버튼보다 스위치 역할에 가깝습니다. 스크린리더 호환성과 폼 내 의도치 않은 submit 방지를 위해 아래 보강을 권장합니다.

-        <button
-            onClick={handleToggle}
-            className={`relative w-[80px] min-w-[80px] h-[36px] shrink-0 rounded-full flex items-center px-2 transition-colors duration-300
-                ${isOn ? 'bg-primary-500' : 'bg-default-gray-400'} ${className}`}
-        >
+        <button
+            type="button"
+            role="switch"
+            aria-checked={isOn}
+            aria-label={ariaLabel}
+            disabled={disabled}
+            onClick={handleToggle}
+            className={`relative w-[80px] min-w-[80px] h-[36px] shrink-0 rounded-full flex items-center px-2 transition-colors duration-300 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500
+                ${isOn ? 'bg-primary-500' : 'bg-default-gray-400'} ${className}`}
+        >

3-11: 비활성화/접근성 지원을 위한 props 추가

API 연동 중 요청 중복/실패 시를 대비해 disabled, 스크린리더용 ariaLabel props를 추가하면 재사용성이 좋아집니다.

 interface IToggleSwitchProps {
     value?: boolean;
     onChange?: (val: boolean) => void;
     onLabel?: string;
     offLabel?: string;
     className?: string;
+    disabled?: boolean;
+    ariaLabel?: string;
 }
 
-export default function ToggleSwitch({ value, onChange, onLabel = 'ON', offLabel = 'OFF', className = '' }: IToggleSwitchProps) {
+export default function ToggleSwitch({ value, onChange, onLabel = 'ON', offLabel = 'OFF', className = '', disabled = false, ariaLabel }: IToggleSwitchProps) {

20-24: disabled 상태에서 토글 차단

비활성화 시 클릭 무시하도록 방지 로직을 추가하면 UX가 안정적입니다.

     const handleToggle = () => {
+        if (disabled) return;
         const next = !isOn;
         setIsOn(next);
         onChange?.(next);
     };

32-40: 매직 넘버(33px/36px) 제거 제안: 레이블을 flex로 좌우 정렬

픽셀 고정값 대신 레이블 컨테이너를 절대 위치 + flex로 배치하면 테마/사이즈 변화에도 안정적으로 정렬됩니다.

-            {/* 텍스트 ON */}
-            <span className={`absolute left-[36px] font-heading3 text-white transition-opacity duration-200 ${isOn ? 'opacity-100' : 'opacity-0'}`}>
-                {onLabel}
-            </span>
-
-            {/* 텍스트 OFF */}
-            <span className={`absolute right-[33px] font-heading3 text-white transition-opacity duration-200 ${!isOn ? 'opacity-100' : 'opacity-0'}`}>
-                {offLabel}
-            </span>
+            {/* 텍스트 레이블 */}
+            <div className="pointer-events-none absolute inset-0 px-4 flex items-center justify-between">
+                <span className={`font-heading3 text-white transition-opacity duration-200 ${isOn ? 'opacity-100' : 'opacity-0'}`}>
+                    {onLabel}
+                </span>
+                <span className={`font-heading3 text-white transition-opacity duration-200 ${!isOn ? 'opacity-100' : 'opacity-0'}`}>
+                    {offLabel}
+                </span>
+            </div>

12-18: 제어/비제어 모드 분리 권장
현재 src/components/common/ToggleSwitch.tsx (12–18줄)에서 value ?? false로 초기화한 뒤 useEffect로 다시 동기화하는 패턴은 부모로부터 전달된 value가 늦게 반영될 때 UI 깜빡임(flicker)을 유발할 수 있습니다.

  • 개선 전 코드
    const [isOn, setIsOn] = useState(value ?? false);
    
    useEffect(() => {
      if (value !== undefined) {
        setIsOn(value);
      }
    }, [value]);
  • 개선 후 예시
    const isControlled = value !== undefined;
    const [internal, setInternal] = useState(false);
    const isOn = isControlled ? (value as boolean) : internal;
    
    const handleToggle = () => {
      if (disabled) return;
      const next = !isOn;
      if (!isControlled) setInternal(next);
      onChange?.(next);
    };
    isControlled 플래그로 제어 모드를 명확히 구분하세요.
    • 내부 상태는 internal로만 관리하고, 제어 모드일 때는 외부 value만 사용합니다.
    • 이 구조에서는 useEffect가 필요 없으므로 제거할 수 있습니다.

위 변경을 적용하면 깜빡임 없이 안정적인 토글 상태 관리가 가능합니다.


44-45: 토글 핸들 방향 반전 검토 요청

현재 isOn=true일 때 핸들이 왼쪽(translate-x-0)에, isOn=false일 때 오른쪽(translate-x-[40px])에 위치해 일반적인 ON=오른쪽, OFF=왼쪽 UX와 반대입니다. 아래와 같이 수정 검토를 부탁드립니다.

  • 파일: src/components/common/ToggleSwitch.tsx
  • 라인: 44–45
- className={`w-6 h-6 bg-white rounded-full shadow-default transition-transform duration-300 ${isOn ? 'translate-x-0' : 'translate-x-[40px]'}`}
+ className={`w-6 h-6 bg-white rounded-full shadow-default transition-transform duration-300 ${isOn ? 'translate-x-[40px]' : 'translate-x-0'}`}
src/components/modal/SettingModal.tsx (1)

45-52: 탭 버튼: type 명시 및 둥근 모서리 클래스 수정(오타), aria-pressed 추가

  • type 미지정 → 폼 내에서 submit 가능성
  • rounding-16는 Tailwind 표준 아님 → rounded-[16px] 등으로 교체
  • 현재 탭 상태를 전달하기 위해 aria-pressed를 추가하면 접근성이 향상됩니다.
-                        <button
+                        <button
+                            type="button"
                             key={tab}
                             onClick={() => setActiveTab(tab as '알람' | '멤버십' | '정보')}
-                            className={`w-full h-[35px] px-4 rounding-16 font-body1 text-left transition 
+                            aria-pressed={activeTab === tab}
+                            className={`w-full h-[35px] px-4 rounded-[16px] font-body1 text-left transition 
                 ${activeTab === tab ? 'bg-primary-500 text-white' : 'bg-default-gray-400 text-default-gray-700'}`}
                         >
♻️ Duplicate comments (7)
src/components/settingTab/InfoSetting.tsx (3)

22-24: apiNickname이 빈 문자열('')일 때 상태 동기화 누락 가능성

현재 falsy 체크로 인해 서버가 빈 문자열을 내려줄 경우 상태가 갱신되지 않습니다. undefined/null만 걸러주세요.

-    const apiNickname = memberData?.result?.username ?? '';
+    const apiNickname = memberData?.result?.username; // string | undefined
@@
-    useEffect(() => {
-        if (apiNickname) {
-            setNickname(apiNickname);
-            setInitialNickname(apiNickname);
-        }
-    }, [apiNickname]);
+    useEffect(() => {
+        if (apiNickname !== undefined && apiNickname !== null) {
+            setNickname(apiNickname);
+            setInitialNickname(apiNickname);
+        }
+    }, [apiNickname]);

Also applies to: 28-34


96-99: 읽기 전용 이메일 입력: pointer-events-none 제거 권장

readOnly만으로 상호작용이 차단되며, pointer-events-none은 텍스트 선택/포커스를 막아 접근성을 해칠 수 있습니다.

-                readOnly
-                onChange={() => {}}
-                className="pointer-events-none"
+                readOnly
+                onChange={() => {}}

107-114: Tailwind 클래스 오타: rounding-32 → 유효한 rounded 유틸로 교체

Tailwind에는 rounding-32가 없습니다. 의도에 따라 rounded-full 또는 임의 값 rounded-[32px]를 사용하세요.

-                    className={`bg-primary-500 px-6 py-2 rounding-32 text-white font-body1 ${resetPending ? 'opacity-60 cursor-not-allowed' : ''}`}
+                    className={`bg-primary-500 px-6 py-2 rounded-full text-white font-body1 ${resetPending ? 'opacity-60 cursor-not-allowed' : ''}`}
src/hooks/notice/useNotice.ts (3)

28-29: TS 오류: typeof에 제네릭 적용 불가 — options 타입 지정 즉시 수정 필요

Parameters<typeof useCoreQuery<T>>() 형태는 TS에서 허용되지 않아 빌드 에러를 유발합니다. 제네릭은 typeof와 함께 사용할 수 없습니다. 아래처럼 수정해 주세요.

-    const useGetNotices = (params: TListParams, options?: Parameters<typeof useCoreQuery<TFetchNoticesResponse>>[2]) => {
+    const useGetNotices = (params: TListParams, options?: Parameters<typeof useCoreQuery>[2]) => {
-    const useGetNoticeDetail = (id: number, options?: Parameters<typeof useCoreQuery<TFetchNoticeDetailResponse>>[2]) =>
+    const useGetNoticeDetail = (id: number, options?: Parameters<typeof useCoreQuery>[2]) =>
-    const useSearchNotices = (params: TSearchParams, options?: Parameters<typeof useCoreQuery<TFetchNoticesResponse>>[2]) => {
+    const useSearchNotices = (params: TSearchParams, options?: Parameters<typeof useCoreQuery>[2]) => {

참고: 보다 강한 타입을 원하시면 TUseQueryCustomOptions(프로젝트에 존재한다면)를 import하여 각 훅의 options에 직접 제네릭을 명시하는 방법도 있습니다.

Also applies to: 45-46, 52-53


47-47: 공지 상세 enabled 조건을 양의 정수 검사로 강화

음수/실수 케이스를 차단하기 위해 Number.isInteger(id) && id > 0가 더 명확합니다.

-            enabled: Number.isFinite(id) && id > 0,
+            enabled: Number.isInteger(id) && id > 0,

26-67: 훅 팩토리 대신 개별 훅 export 권장 (호환을 위해 팩토리는 유지 가능)

현재 useNotice().useGetXxx() 패턴은 호출이 한 단계 늘고, Rules of Hooks 린터/트리셰이킹 친화성 측면에서도 불리합니다. 개별 훅을 최상위 export로 노출하고, 기존 사용처 호환을 위해 간단한 팩토리만 유지하는 구조를 권장드립니다.

아래와 같이 이 블록을 대체하면 됩니다:

-export function useNotice() {
-    // 공지 목록 조회 훅
-    const useGetNotices = (params: TListParams, options?: Parameters<typeof useCoreQuery>[2]) => {
-        const stable = useMemo(
-            () => ({
-                category: params.category,
-                page: params.page,
-                size: params.size,
-            }),
-            [params.category, params.page, params.size],
-        );
-
-        return useCoreQuery<TFetchNoticesResponse>(noticeKeys.list(stable), () => getNotices(stable), {
-            placeholderData: keepPreviousData,
-            ...options,
-        });
-    };
-
-    // 공지 상세
-    const useGetNoticeDetail = (id: number, options?: Parameters<typeof useCoreQuery>[2]) =>
-        useCoreQuery<TFetchNoticeDetailResponse>(noticeKeys.detail(id), () => getNoticeDetail(id), {
-            enabled: Number.isFinite(id) && id > 0,
-            ...options,
-        });
-
-    // 공지 검색
-    const useSearchNotices = (params: TSearchParams, options?: Parameters<typeof useCoreQuery>[2]) => {
-        const stable = useMemo(() => ({ ...params, keyword: params.keyword.trim() }), [params.keyword, params.page, params.size, params.category]);
-
-        return useCoreQuery<TFetchNoticesResponse>(noticeKeys.search(stable), () => searchNotices(stable), {
-            enabled: stable.keyword.length > 0,
-            placeholderData: keepPreviousData,
-            ...options,
-        });
-    };
-
-    return {
-        useGetNotices,
-        useGetNoticeDetail,
-        useSearchNotices,
-    };
-}
+// 개별 훅 export
+export const useGetNotices = (params: TListParams, options?: Parameters<typeof useCoreQuery>[2]) => {
+    const stable = useMemo(
+        () => ({ category: params.category, page: params.page, size: params.size }),
+        [params.category, params.page, params.size],
+    );
+    return useCoreQuery<TFetchNoticesResponse>(noticeKeys.list(stable), () => getNotices(stable), {
+        placeholderData: keepPreviousData,
+        ...options,
+    });
+};
+
+export const useGetNoticeDetail = (id: number, options?: Parameters<typeof useCoreQuery>[2]) =>
+    useCoreQuery<TFetchNoticeDetailResponse>(noticeKeys.detail(id), () => getNoticeDetail(id), {
+        enabled: Number.isInteger(id) && id > 0,
+        ...options,
+    });
+
+export const useSearchNotices = (params: TSearchParams, options?: Parameters<typeof useCoreQuery>[2]) => {
+    const stable = useMemo(() => ({ ...params, keyword: params.keyword.trim() }), [params.keyword, params.page, params.size, params.category]);
+    return useCoreQuery<TFetchNoticesResponse>(noticeKeys.search(stable), () => searchNotices(stable), {
+        enabled: stable.keyword.length > 0,
+        placeholderData: keepPreviousData,
+        ...options,
+    });
+};
+
+// (선택) 기존 호출부 호환을 위한 팩토리 유지
+export function useNotice() {
+    return { useGetNotices, useGetNoticeDetail, useSearchNotices };
+}

추가로, 타입 안전한 select를 자주 사용하신다면 각 훅에 제네릭을 노출하는 방식도 고려해 보세요:

  • 예: export const useGetNotices = <TData = TFetchNoticesResponse>(..., options?: TUseQueryCustomOptions<TFetchNoticesResponse, TData>) => useCoreQuery<TFetchNoticesResponse, TData>(...);
src/pages/setting/PaymentHistory.tsx (1)

39-41: 뒤로가기 UX: 히스토리 우선, 폴백은 홈 (이전 코멘트와 동일 제안)

브라우저 히스토리가 있을 땐 뒤로가기, 없으면 홈으로 가는 패턴이 자연스럽습니다. 아이콘 버튼에 aria-label도 추가해 주세요.

-                <button onClick={() => navigate('/home')}>
+                <button
+                    aria-label="뒤로가기"
+                    onClick={() => (window.history.length > 1 ? navigate(-1) : navigate('/home'))}
+                >
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d44260 and 8971e88.

📒 Files selected for processing (5)
  • src/components/common/ToggleSwitch.tsx (1 hunks)
  • src/components/modal/SettingModal.tsx (3 hunks)
  • src/components/settingTab/InfoSetting.tsx (1 hunks)
  • src/hooks/notice/useNotice.ts (1 hunks)
  • src/pages/setting/PaymentHistory.tsx (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/components/modal/SettingModal.tsx (2)
src/hooks/auth/useAuth.ts (1)
  • useAuth (5-14)
src/pages/setting/SettingEntryPage.tsx (1)
  • SettingEntryPage (8-30)
src/hooks/notice/useNotice.ts (5)
src/hooks/customQuery.ts (1)
  • useCoreQuery (7-18)
src/types/notice/notice.ts (2)
  • TFetchNoticesResponse (12-18)
  • TFetchNoticeDetailResponse (30-30)
src/api/notice/notice.keys.ts (1)
  • noticeKeys (1-12)
src/api/notice/notice.ts (3)
  • getNotices (6-15)
  • getNoticeDetail (18-21)
  • searchNotices (24-36)
src/pages/notice/Notice.tsx (3)
  • Notice (13-119)
  • getNotices (29-55)
  • notice (97-104)
src/components/settingTab/InfoSetting.tsx (4)
src/hooks/auth/useAccount.ts (2)
  • useAccount (23-55)
  • QUERY_KEYS (18-21)
src/api/auth/account.ts (1)
  • changeNickname (13-16)
src/components/common/EditableInputBox.tsx (1)
  • EditableInputBox (22-160)
src/constants/policies.ts (2)
  • TERMS_URL (1-1)
  • PRIVACY_URL (2-2)
src/pages/setting/PaymentHistory.tsx (3)
src/components/layout/Header.tsx (1)
  • Header (18-90)
src/components/payment/PaymentRow.tsx (1)
  • PaymentRow (11-21)
src/components/common/navigator.tsx (1)
  • Navigator (10-43)
🪛 Biome (2.1.2)
src/components/modal/SettingModal.tsx

[error] 59-62: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🔇 Additional comments (7)
src/components/common/ToggleSwitch.tsx (1)

38-40: OFF 라벨 위치 33px 조정: 시각 보정으로 보이며 기능 영향 없음

의도한 UI 미세 조정이라면 현재 변경 그대로 문제 없습니다.

src/components/settingTab/InfoSetting.tsx (2)

42-43: React Query 키 상수화 잘 적용됨

QUERY_KEYS로 invalidateQueries 키를 통일한 점 좋습니다. 오타/불일치 리스크가 줄어듭니다.


118-136: 약관/개인정보 링크 a 태그 전환 적절

외부 링크로 새 탭 오픈 및 noopener 보안 속성까지 포함되어 있어 적절합니다.

src/hooks/notice/useNotice.ts (1)

29-36: params 안정화(useMemo) 접근 적절합니다

목록 조회에서 queryKey에 들어가는 파라미터를 구조 분해해 메모이즈한 점 좋습니다. 불필요한 리렌더/재요청을 줄이는 데 도움이 됩니다.

src/pages/setting/PaymentHistory.tsx (3)

67-68: 페이지네이터 연동 깔끔합니다

current/end/onClick 프로퍼티 연동과 그룹 페이징 컴포넌트 사용이 명확합니다.


4-4: 문제 없음: Navigator 경로 및 기본 내보내기 일치 확인 완료

src/components/common/navigator.tsx 파일이 소문자 navigator.tsx로 존재하며, export default function Navigator(...) 시그니처도 정확히 일치합니다. CI 빌드에서도 경로 대소문자 문제 없이 정상 동작합니다.


60-62: 정적 데이터에 고유 ID가 없어 기존 key 유지

현재 PaymentHistory.tsxpageData는 useMemo로 하드코딩된 정적 배열을 사용하고 있어 id 같은 고유 식별자가 존재하지 않습니다. 따라서 현 시점에서는 기존의 key={`${item.orderNumber}-${index}`} 방식을 그대로 유지해도 무방합니다.
향후 실제 결제 이력 API 응답에 id 필드가 추가되면, 그때 key={item.id}로 교체하길 권장드립니다.

Comment on lines +56 to 64
<button
onClick={handleLogout}
disabled={logoutPending}
className="font-body2 text-default-gray-800 cursor-pointer flex items-center space-x-2 mt-6 disabled:opacity-60"
>
<LogoutSvg className="w-4 h-4" fill="none" stroke="CurrentColor" />
<span>로그아웃</span>
</div>
<span>{logoutPending ? '로그아웃 중...' : '로그아웃'}</span>
</button>
</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

버튼 type 명시 및 a11y 보완

기본 type=submit로 인한 폼 오동작을 방지하려면 type="button"을 명시하세요. 정적 분석 경고도 해소됩니다.

-                <button
+                <button
+                    type="button"
                     onClick={handleLogout}
                     disabled={logoutPending}
                     className="font-body2 text-default-gray-800 cursor-pointer flex items-center space-x-2 mt-6 disabled:opacity-60"
                 >

아래 스크립트로 SettingModal.tsx 내 버튼이 폼(<form>) 내부에 있는지, handleLogout 주변 컨텍스트를 확인해 보겠습니다.


🏁 Script executed:

#!/bin/bash
# src/components/modal/SettingModal.tsx에서 form 태그 검색
rg "<form" -n src/components/modal/SettingModal.tsx || true

echo "------"

# handleLogout 호출 위치 주변 컨텍스트 확인
rg "handleLogout" -n src/components/modal/SettingModal.tsx -A 5 -B 5 || true

Length of output: 1153


SettingModal.tsx: 버튼에 type="button" 명시하기

버튼 요소의 기본 type"submit"이므로, 추후 폼 내부로 이동되거나 정적 분석 경고를 방지하기 위해 명시적으로 type="button"을 추가해 주세요.

• 파일: src/components/modal/SettingModal.tsx
• 위치: 56–64행

-                <button
+                <button
+                    type="button"
                     onClick={handleLogout}
                     disabled={logoutPending}
                     className="font-body2 text-default-gray-800 cursor-pointer flex items-center space-x-2 mt-6 disabled:opacity-60"
                 >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
onClick={handleLogout}
disabled={logoutPending}
className="font-body2 text-default-gray-800 cursor-pointer flex items-center space-x-2 mt-6 disabled:opacity-60"
>
<LogoutSvg className="w-4 h-4" fill="none" stroke="CurrentColor" />
<span>로그아웃</span>
</div>
<span>{logoutPending ? '로그아웃 중...' : '로그아웃'}</span>
</button>
</div>
<button
type="button"
onClick={handleLogout}
disabled={logoutPending}
className="font-body2 text-default-gray-800 cursor-pointer flex items-center space-x-2 mt-6 disabled:opacity-60"
>
<LogoutSvg className="w-4 h-4" fill="none" stroke="CurrentColor" />
<span>{logoutPending ? '로그아웃 중...' : '로그아웃'}</span>
</button>
🧰 Tools
🪛 Biome (2.1.2)

[error] 59-62: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🤖 Prompt for AI Agents
In src/components/modal/SettingModal.tsx around lines 56 to 64, the logout
button lacks an explicit type attribute; update the button JSX to include
type="button" on the element while preserving onClick, disabled, className and
children so it won't act as a submit button; save and run linters/tests to
ensure no warnings remain.

disabled={logoutPending}
className="font-body2 text-default-gray-800 cursor-pointer flex items-center space-x-2 mt-6 disabled:opacity-60"
>
<LogoutSvg className="w-4 h-4" fill="none" stroke="CurrentColor" />
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

SVG stroke 색상 값 표기 통일 권장

CSS 색상 키워드 currentColor는 소문자 표기가 일반적입니다. 동작엔 문제 없으나 일관성 차원에서 교체를 권장합니다.

-                    <LogoutSvg className="w-4 h-4" fill="none" stroke="CurrentColor" />
+                    <LogoutSvg className="w-4 h-4" fill="none" stroke="currentColor" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<LogoutSvg className="w-4 h-4" fill="none" stroke="CurrentColor" />
<LogoutSvg className="w-4 h-4" fill="none" stroke="currentColor" />
🤖 Prompt for AI Agents
In src/components/modal/SettingModal.tsx around line 61, the SVG uses
stroke="CurrentColor" which should be normalized to the lowercase CSS keyword;
update the prop to stroke="currentColor" to keep casing consistent across the
codebase and avoid style inconsistencies.

Comment on lines 1 to 7
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { useQueryClient } from '@tanstack/react-query';

import { PRIVACY_URL, TERMS_URL } from '@/constants/policies';

import { QUERY_KEYS, useAccount } from '@/hooks/auth/useAccount';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

AxiosError로 에러 타입 명시하고 401 처리는 인터셉터로 위임하는 것을 권장

any 사용을 지양하고, 에러 메시지 파싱 유틸은 AxiosError 기준으로 안전하게 처리하는 편이 좋습니다. 401 분기는 axios 인터셉터에서 공통 처리하면 중복/누락을 줄일 수 있습니다.

적용 제안:

+import type { AxiosError } from 'axios';
@@
-const getApiErrorMessage = (err: any, fallback: string) => err?.response?.data?.message ?? (err?.response?.status === 401 ? '로그인이 필요합니다.' : fallback);
+const getApiErrorMessage = (err: AxiosError | unknown, fallback: string) =>
+  (err as AxiosError)?.response?.data?.message ?? fallback;
@@
-        onError: (err: any) => alert(getApiErrorMessage(err, '닉네임 변경에 실패했습니다.')),
+        onError: (err: unknown) => alert(getApiErrorMessage(err, '닉네임 변경에 실패했습니다.')),
@@
-        onError: (err: any) => alert(getApiErrorMessage(err, '초기화에 실패했습니다.')),
+        onError: (err: unknown) => alert(getApiErrorMessage(err, '초기화에 실패했습니다.')),

추가로, axiosInstance 인터셉터에서 error?.response?.status === 401을 공통 처리해 주세요(토큰 정리, 라우팅, 토스트 표준화 등).

Also applies to: 14-15, 47-48, 58-59

🤖 Prompt for AI Agents
In src/components/settingTab/InfoSetting.tsx lines 1-7 (and also review spots at
14-15, 47-48, 58-59): replace use of any when handling errors with AxiosError
from axios, update any local error-parsing utilities to accept and safely read
AxiosError.response?.data/message, and remove local 401 branching so that
authentication failures are handled centrally in the axiosInstance interceptor;
in the interceptor implement standardized 401 handling (token cleanup, redirect
to login, and toast/error reporting) and ensure callers here only surface
non-auth errors using the typed AxiosError for safe access.

@@ -1,49 +1,43 @@
// 결제 내역 확인
import { useState } from 'react';
import { useMemo, useState } from 'react';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

옵션 2 적용 시 불필요한 import 정리

useMemo를 제거한다면 import도 함께 정리해주세요.

-import { useMemo, useState } from 'react';
+import { useState } from 'react';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { useMemo, useState } from 'react';
import { useState } from 'react';
🤖 Prompt for AI Agents
In src/pages/setting/PaymentHistory.tsx around line 1, the import currently
includes useMemo but that hook is no longer used; remove useMemo from the import
list (leave any other imports such as useState intact) so the import statement
only imports actually used symbols to avoid unused-import lint errors.

Comment on lines +11 to 14
{ orderNumber: '202219486', date: '2024.01.15', amount: '₩2,900', method: '카카오페이', status: '환불처리' },
{ orderNumber: '202219487', date: '2023.05.15', amount: '₩2,900', method: '신한카드', status: '결제 완료' },
{ orderNumber: '202219487', date: '2023.05.15', amount: '₩2,900', method: '신한카드', status: '결제 완료' },
];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

더미 데이터 중복값 및 타입 명시

orderNumber가 중복되어 있어(라인 12-13) 유니크 키 가정이 흔들릴 수 있습니다. 또한 더미 데이터에도 타입을 명시해 두면 이후 교체가 수월합니다.

-const dummyData = [
+type Payment = {
+    orderNumber: string;
+    date: string;
+    amount: string;
+    method: string;
+    status: string;
+};
+const dummyData: Payment[] = [
     { orderNumber: '202219486', date: '2024.01.15', amount: '₩2,900', method: '카카오페이', status: '환불처리' },
-    { orderNumber: '202219487', date: '2023.05.15', amount: '₩2,900', method: '신한카드', status: '결제 완료' },
-    { orderNumber: '202219487', date: '2023.05.15', amount: '₩2,900', method: '신한카드', status: '결제 완료' },
+    { orderNumber: '202219487', date: '2023.05.15', amount: '₩2,900', method: '신한카드', status: '결제 완료' },
+    { orderNumber: '202219488', date: '2023.04.02', amount: '₩4,900', method: '우리카드', status: '결제 완료' },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{ orderNumber: '202219486', date: '2024.01.15', amount: '₩2,900', method: '카카오페이', status: '환불처리' },
{ orderNumber: '202219487', date: '2023.05.15', amount: '₩2,900', method: '신한카드', status: '결제 완료' },
{ orderNumber: '202219487', date: '2023.05.15', amount: '₩2,900', method: '신한카드', status: '결제 완료' },
];
type Payment = {
orderNumber: string;
date: string;
amount: string;
method: string;
status: string;
};
const dummyData: Payment[] = [
{ orderNumber: '202219486', date: '2024.01.15', amount: '₩2,900', method: '카카오페이', status: '환불처리' },
{ orderNumber: '202219487', date: '2023.05.15', amount: '₩2,900', method: '신한카드', status: '결제 완료' },
{ orderNumber: '202219488', date: '2023.04.02', amount: '₩4,900', method: '우리카드', status: '결제 완료' },
];
🤖 Prompt for AI Agents
In src/pages/setting/PaymentHistory.tsx around lines 11 to 14, the dummy data
contains a duplicate orderNumber (lines 12-13) and lacks an explicit type;
change the duplicated entry so each orderNumber is unique and add a TypeScript
type for the array (e.g., define an interface/type for a payment record with
orderNumber, date, amount, method, status and annotate the dummy array as that
type) so future replacements are type-checked and the unique-key assumption
holds.

Comment on lines +19 to +25
// 페이지네이션 상태
const [currentPage, setCurrentPage] = useState(1);
const itemsPerPage = 10;

// 전체 페이지 수
const totalPages = Math.max(1, Math.ceil(dummyData.length / itemsPerPage));

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

데이터 감소 시 현재 페이지 클램프(미래 대비)

API 연동 후 데이터 양이 줄어들면 currentPage > totalPages가 될 수 있습니다. 효과로 보정해두면 안전합니다.

     const totalPages = Math.max(1, Math.ceil(dummyData.length / itemsPerPage));
 
+    // 데이터 변화로 페이지 수가 줄어든 경우 현재 페이지 보정
+    useEffect(() => {
+        if (currentPage > totalPages) {
+            setCurrentPage(totalPages);
+        }
+    }, [totalPages, currentPage]);

추가로, 상단 import에 useEffect를 포함해 주세요:

-import { useMemo, useState } from 'react';
+import { useMemo, useState, useEffect } from 'react';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 페이지네이션 상태
const [currentPage, setCurrentPage] = useState(1);
const itemsPerPage = 10;
// 전체 페이지 수
const totalPages = Math.max(1, Math.ceil(dummyData.length / itemsPerPage));
// at the top of the file, include useEffect
-import { useMemo, useState } from 'react';
+import { useMemo, useState, useEffect } from 'react';
// 페이지네이션 상태
const [currentPage, setCurrentPage] = useState(1);
const itemsPerPage = 10;
// 전체 페이지 수
const totalPages = Math.max(1, Math.ceil(dummyData.length / itemsPerPage));
+ // 데이터 변화로 페이지 수가 줄어든 경우 현재 페이지 보정
+ useEffect(() => {
+ if (currentPage > totalPages) {
+ setCurrentPage(totalPages);
+ }
+ }, [totalPages, currentPage]);
🤖 Prompt for AI Agents
In src/pages/setting/PaymentHistory.tsx around lines 19 to 25, when the data
size changes currentPage can become greater than totalPages; add a useEffect
(and import useEffect at top) that watches [currentPage, totalPages] or
preferably [totalPages] and clamps the page by calling setCurrentPage(prev =>
Math.min(prev, totalPages)) so currentPage is never > totalPages; ensure
useEffect runs after dummyData/totalPages updates to adjust the page
accordingly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (12)
src/pages/home/HomePage.tsx (2)

17-17: 주석 처리된 useDeviceToken의 의도 명시 또는 제거 권장

일시 비활성화라면 사유와 복구 조건을 TODO로 남겨 추후 유지보수성을 높여주세요. 불필요하다면 완전 제거도 고려해 주세요.

적용 예:

-    // useDeviceToken();
+    // TODO: FCM 디바이스 토큰 전송 기능은 임시 비활성화됨 (백엔드 인증/권한 이슈 해결 후 재활성화)
+    // useDeviceToken();

6-7: 불필요한 빈 줄 제거

이전에 Lint가 지적한 빈 줄이 그대로 남아 있습니다. 제거해 주세요.

-
 import Banner from '@/components/home/banner';
src/pages/setting/PaymentHistory.tsx (5)

1-1: useEffect import 추가 및(또는) useMemo 제거 시 import 정리

아래 옵션 적용 여부에 따라 import도 함께 정리해 주세요.

옵션 A) 현재 코드 유지 + useEffect 도입(페이지 클램프 적용 시)

-import { useMemo, useState } from 'react';
+import { useMemo, useState, useEffect } from 'react';

옵션 B) useMemo 제거(아래 코멘트의 옵션 2 적용 시)

-import { useMemo, useState } from 'react';
+import { useState, useEffect } from 'react';

11-13: 더미 데이터 중복 키 수정 및 타입 명시 권장

주문번호가 중복되어 키 충돌 가능성이 있고, 타입을 명시해두면 이후 API 교체 시에도 이점이 있습니다.

중복 키 수정(예시):

-    { orderNumber: '202219487', date: '2023.05.15', amount: '₩2,900', method: '신한카드', status: '결제 완료' },
+    { orderNumber: '202219488', date: '2023.04.02', amount: '₩4,900', method: '우리카드', status: '결제 완료' },

배열 타입 명시(선언부 바로 위에 추가해 주세요):

type Payment = {
  orderNumber: string;
  date: string;
  amount: string;
  method: string;
  status: string;
};

// 배열 선언부에 타입 적용
const dummyData: Payment[] = [
  // ...
];

23-25: 데이터 감소 시 현재 페이지 클램프 추가

데이터 변화로 totalPages가 줄어드는 경우를 대비해 currentPage를 보정해 주세요. 상단 import에 useEffect도 포함해야 합니다.

     const totalPages = Math.max(1, Math.ceil(dummyData.length / itemsPerPage));
 
+    // 데이터 변화로 페이지 수가 줄어든 경우 현재 페이지 보정
+    useEffect(() => {
+        if (currentPage > totalPages) {
+            setCurrentPage(totalPages);
+        }
+    }, [totalPages, currentPage]);

27-31: useMemo 의존성 보완 또는 단순화(권장: 단순화)

현재 deps에 dummyData가 빠져 있어 린트 경고 소지가 있습니다. 단, 이 계산은 가벼우므로 메모화 제거도 충분합니다.

옵션 1) 의존성 보완

-    }, [currentPage, itemsPerPage]);
+    }, [currentPage, itemsPerPage, dummyData]);

옵션 2) 메모화 제거(간단, 추천)

-    const pageData = useMemo(() => {
-        const start = (currentPage - 1) * itemsPerPage;
-        const end = start + itemsPerPage;
-        return dummyData.slice(start, end);
-    }, [currentPage, itemsPerPage]);
+    const start = (currentPage - 1) * itemsPerPage;
+    const end = start + itemsPerPage;
+    const pageData = dummyData.slice(start, end);

옵션 2 적용 시 상단 import에서 useMemo를 제거해 주세요(별도 코멘트 참고).


39-41: 뒤로가기 UX 개선: 히스토리 우선, 폴백은 홈 + 접근성 라벨

브라우저 히스토리가 있으면 뒤로가기, 없으면 홈으로 이동하는 패턴을 추천합니다. 아이콘 버튼에 aria-label도 추가해 주세요.

-                <button onClick={() => navigate('/home')}>
+                <button
+                    aria-label="뒤로가기"
+                    onClick={() => (window.history.length > 1 ? navigate(-1) : navigate('/home'))}
+                >
src/components/settingTab/InfoSetting.tsx (5)

64-69: 닉네임 제출 검증 로직 개선 👍

trim 처리, 빈 값/동일 값/진행 중 가드로 불필요한 호출을 방지했습니다. 이전 피드백을 잘 반영하셨습니다.


14-14: 에러 타입 any 제거 및 401 처리는 인터셉터로 이관 권장

에러를 any로 다루면 안전하지 않고, 401 분기는 axios 인터셉터에서 공통 처리하는 편이 중복/누락을 줄입니다. 아래처럼 타입을 명시하고 401 분기는 제거해 주세요.

적용 diff:

-const getApiErrorMessage = (err: any, fallback: string) => err?.response?.data?.message ?? (err?.response?.status === 401 ? '로그인이 필요합니다.' : fallback);
+const getApiErrorMessage = (err: AxiosError | unknown, fallback: string) =>
+  (err as AxiosError)?.response?.data?.message ?? fallback;
-        onError: (err: any) => alert(getApiErrorMessage(err, '닉네임 변경에 실패했습니다.')),
+        onError: (err: unknown) => alert(getApiErrorMessage(err, '닉네임 변경에 실패했습니다.')),
-        onError: (err: any) => alert(getApiErrorMessage(err, '초기화에 실패했습니다.')),
+        onError: (err: unknown) => alert(getApiErrorMessage(err, '초기화에 실패했습니다.')),

추가로 파일 상단에 타입 import가 필요합니다:

import type { AxiosError } from 'axios';

그리고 axiosInstance(response interceptor)에서 401을 공통 처리해 주세요(토큰/세션 정리, 라우팅, 토스트 등).

Also applies to: 50-51, 61-62


23-24: 빈 문자열 닉네임(apiNickname) 동기화 누락 가능성

apiNickname이 빈 문자열('')일 때 현재 조건문이 false로 평가되어 상태가 갱신되지 않습니다. 기본값 할당을 제거하고 undefined/null만 거르는 방식으로 수정해 주세요.

-    const apiNickname = memberData?.result?.username ?? '';
+    const apiNickname = memberData?.result?.username;
-    useEffect(() => {
-        if (apiNickname) {
+    useEffect(() => {
+        if (apiNickname !== undefined && apiNickname !== null) {
             setNickname(apiNickname);
             setInitialNickname(apiNickname);
         }
     }, [apiNickname]);

Also applies to: 29-34


99-103: 읽기 전용 이메일 입력: pointer-events-none 제거 권장

readOnly만으로도 편집 차단이 됩니다. pointer-events-none은 텍스트 선택/포커스를 막아 접근성을 해칩니다.

-                className="pointer-events-none"
+                className=""

시각적 비활성화가 필요하다면 opacity/cursor 클래스만 별도로 적용하고, 필요 시 aria-readonly="true"를 추가해 주세요.


114-114: Tailwind 클래스 오탈자: rounding-32 → rounded-[32px] (또는 rounded-full)

Tailwind 표준에 rounding-32는 없습니다. 의도 반영을 위해 유효한 rounding 유틸로 교체하세요.

-                    className={`bg-primary-500 px-6 py-2 rounding-32 text-white font-body1 ${resetPending ? 'opacity-60 cursor-not-allowed' : ''}`}
+                    className={`bg-primary-500 px-6 py-2 rounded-[32px] text-white font-body1 ${resetPending ? 'opacity-60 cursor-not-allowed' : ''}`}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8971e88 and 27b7d52.

📒 Files selected for processing (3)
  • src/components/settingTab/InfoSetting.tsx (1 hunks)
  • src/pages/home/HomePage.tsx (3 hunks)
  • src/pages/setting/PaymentHistory.tsx (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/pages/setting/PaymentHistory.tsx (3)
src/components/layout/Header.tsx (1)
  • Header (18-90)
src/components/payment/PaymentRow.tsx (1)
  • PaymentRow (11-21)
src/components/common/navigator.tsx (1)
  • Navigator (10-43)
src/pages/home/HomePage.tsx (4)
src/hooks/home/useUserGrade.ts (1)
  • useUserGrade (7-12)
src/hooks/auth/useAccount.ts (1)
  • useAccount (23-55)
src/components/home/level.tsx (1)
  • Level (9-48)
src/types/home/level.ts (1)
  • IGradeInfo (7-13)
🔇 Additional comments (7)
src/pages/home/HomePage.tsx (1)

4-4: useAccount 도입 적절

계정 관련 쿼리/뮤테이션을 한 곳에서 제공하는 패턴으로 일관성이 좋아졌습니다. HomePage에서의 사용도 규칙(Top-level hook 호출)을 준수하고 있습니다.

src/pages/setting/PaymentHistory.tsx (4)

4-4: Navigator 도입 적절합니다

공통 페이지 네비게이션 컴포넌트 사용으로 UI/UX 및 유지보수성이 좋아졌습니다.


60-68: 빈 상태 처리 LGTM

데이터 없음 시 안내 메시지 노출 로직이 명확하고 접근성도 무난합니다.


73-74: Navigator 기반 페이지네이션 도입 좋습니다

그룹 단위 페이지 전환 UX가 일관되고 재사용성도 높습니다.


67-68: 키에서 index 제거 전 주문번호 유일성 확인 필요

현재 dummyData에 동일한 orderNumber: '202219487'가 2번 포함되어 있어, 아래처럼 key={item.orderNumber}만 사용하면 개발 환경에서 중복 키 이슈가 발생합니다.
실서비스 API가 orderNumber의 유일성을 보장하는지 확인하고, (테스트용) dummyData에서도 중복을 제거한 뒤에 아래와 같이 수정하세요.

- pageData.map((item, index) => <PaymentRow key={`${item.orderNumber}-${index}`} data={item} />)
+ pageData.map(item => <PaymentRow key={item.orderNumber} data={item} />)

• 파일: src/pages/setting/PaymentHistory.tsx
• 확인 사항:

  • 테스트용 dummyData 중복 제거
  • 실서비스 API의 orderNumber 유일성 보장 여부
src/components/settingTab/InfoSetting.tsx (2)

110-117: 초기화 버튼 타입/비활성 처리 👍

type="button", disabled 및 pending 상태 처리까지 깔끔합니다. confirm 흐름과 연동도 적절합니다.


121-129: 약관 링크 보안 속성 적절

target="_blank" + rel="noopener noreferrer" 적용이 적절합니다. TERMS_URL 상수 사용도 좋습니다.


import { TERMS_URL } from '@/constants/policies';

import { QUERY_KEYS, useAccount } from '@/hooks/auth/useAccount';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

QUERY_KEYS는 공용 상수 모듈로 분리 권장

hooks 레이어(useAccount)에서 QUERY_KEYS를 가져오면 레이어 결합/순환 의존 위험이 있습니다. constants/queryKeys.ts 같은 공용 상수 모듈로 옮기고, 여기서 import하는 형태로 정리하면 유지보수성이 좋아집니다.

🤖 Prompt for AI Agents
In src/components/settingTab/InfoSetting.tsx around line 7, avoid importing
QUERY_KEYS from the hooks layer; extract QUERY_KEYS into a shared constants
module (e.g., src/constants/queryKeys.ts) that exports the constant, update
InfoSetting.tsx to import QUERY_KEYS from that new module, and update the
useAccount hook to also import QUERY_KEYS from the same constants module so the
hooks layer no longer exposes its constants to higher layers, preventing
coupling/circular-deps.

const next = res.result.username;
setNickname(next);
setInitialNickname(next);
localStorage.setItem('nickname', next);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

로그아웃/탈퇴 시 localStorage 'nickname' 제거 확인 필요

닉네임을 localStorage에 저장하지만 제거 로직이 누락되면 로그아웃 후에도 남을 수 있습니다. 로그아웃/탈퇴 성공 시 removeItem('nickname') 처리 여부를 확인해 주세요. 필요 시 onSuccess 콜백 또는 공통 logout 훅에 정리 코드를 추가하세요.

다음 스크립트로 저장/삭제 사용처를 점검할 수 있습니다:


🏁 Script executed:

#!/bin/bash
# 닉네임 저장/조회/삭제 위치 점검
echo "=== nickname 저장/조회/삭제 위치 ==="
rg -n "localStorage\.(setItem|getItem|removeItem)\(['\"]nickname['\"]" -A 2 -B 2

echo -e "\n=== 로그아웃/탈퇴 관련 훅/페이지 후보 ==="
rg -n "logout|logOut|signOut|deleteAccount|DeleteConfirm" -A 5 -B 5

echo -e "\n=== localStorage 전체 초기화 여부 ==="
rg -n "localStorage\.clear\(" -A 1 -B 1

Length of output: 13604


localStorage ‘nickname’ 제거 로직 추가 필요

로그아웃 및 회원 탈퇴 성공 시 ‘nickname’이 localStorage에 남아 있어, 아래 두 곳의 onSuccess 콜백에 localStorage.removeItem('nickname') 처리를 추가해 주세요.

  • src/components/modal/SettingModal.tsx
    • handleLogout onSuccess (현재 localStorage.removeItem('accessToken') 바로 아래)
  • src/pages/setting/DeleteConfirmPage.tsx
    • deleteAccount onSuccess (현재 localStorage.removeItem('accessToken') 바로 아래)
🤖 Prompt for AI Agents
In src/components/modal/SettingModal.tsx (handleLogout onSuccess, add just after
the existing localStorage.removeItem('accessToken')) and in
src/pages/setting/DeleteConfirmPage.tsx (deleteAccount onSuccess, add just after
the existing localStorage.removeItem('accessToken')), remove the persisted
nickname from localStorage by calling localStorage.removeItem('nickname');
ensure the call is placed immediately after the accessToken removal in both
onSuccess callbacks so nickname does not persist after logout or account
deletion.

Comment on lines +43 to +45
qc.invalidateQueries({ queryKey: QUERY_KEYS.memberInfo });
qc.invalidateQueries({ queryKey: QUERY_KEYS.memberGrade });
qc.setQueryData(['userGrade'], (old: any) => (old ? { ...old, result: { ...old.result, username: next } } : old));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

쿼리 키 불일치: invalidate는 memberGrade, setQueryData는 userGrade를 사용

invalidateQueries는 QUERY_KEYS.memberGrade를 사용하고 setQueryData는 ['userGrade']로 별도 키를 업데이트합니다. 키 불일치로 캐시가 갱신되지 않을 수 있습니다. 동일 키(상수)를 사용하세요.

-                qc.setQueryData(['userGrade'], (old: any) => (old ? { ...old, result: { ...old.result, username: next } } : old));
+                qc.setQueryData(QUERY_KEYS.memberGrade, (old: any) =>
+                  (old ? { ...old, result: { ...old.result, username: next } } : old)
+                );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
qc.invalidateQueries({ queryKey: QUERY_KEYS.memberInfo });
qc.invalidateQueries({ queryKey: QUERY_KEYS.memberGrade });
qc.setQueryData(['userGrade'], (old: any) => (old ? { ...old, result: { ...old.result, username: next } } : old));
qc.invalidateQueries({ queryKey: QUERY_KEYS.memberInfo });
qc.invalidateQueries({ queryKey: QUERY_KEYS.memberGrade });
qc.setQueryData(QUERY_KEYS.memberGrade, (old: any) =>
(old ? { ...old, result: { ...old.result, username: next } } : old)
);
🤖 Prompt for AI Agents
In src/components/settingTab/InfoSetting.tsx around lines 43 to 45, the code
invalidates QUERY_KEYS.memberGrade but calls qc.setQueryData with the literal
key ['userGrade'], causing a query-key mismatch so the intended cache entry
won't be updated; change qc.setQueryData to use the same query key constant
(e.g., QUERY_KEYS.memberGrade) so both invalidateQueries and setQueryData target
the identical cache key and update the cached username consistently.

Comment on lines +131 to +141
<a
href="#"
onClick={(e) => {
e.preventDefault();
alert('해당 기능은 추후 제공 예정입니다.');
}}
className="w-full flex items-center justify-between py-3 px-1 text-left font-body2 text-default-gray-800"
>
개인정보 처리방침
<ChevronForward width={20} height={20} fill="#000000" />
</button>
</a>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

내비게이션 없는 앵커 대신 버튼 사용 권장

현재 "#" 앵커를 클릭 막고 alert만 띄웁니다. 의미상 버튼이 더 적절하며, 불필요한 스크롤 점프/주소 변조를 방지할 수 있습니다.

-                <a
-                    href="#"
-                    onClick={(e) => {
-                        e.preventDefault();
-                        alert('해당 기능은 추후 제공 예정입니다.');
-                    }}
-                    className="w-full flex items-center justify-between py-3 px-1 text-left font-body2 text-default-gray-800"
-                >
+                <button
+                    type="button"
+                    onClick={() => {
+                        alert('해당 기능은 추후 제공 예정입니다.');
+                    }}
+                    className="w-full flex items-center justify-between py-3 px-1 text-left font-body2 text-default-gray-800"
+                >
                   개인정보 처리방침
                   <ChevronForward width={20} height={20} fill="#000000" />
-                </a>
+                </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<a
href="#"
onClick={(e) => {
e.preventDefault();
alert('해당 기능은 추후 제공 예정입니다.');
}}
className="w-full flex items-center justify-between py-3 px-1 text-left font-body2 text-default-gray-800"
>
개인정보 처리방침
<ChevronForward width={20} height={20} fill="#000000" />
</button>
</a>
<button
type="button"
onClick={() => {
alert('해당 기능은 추후 제공 예정입니다.');
}}
className="w-full flex items-center justify-between py-3 px-1 text-left font-body2 text-default-gray-800"
>
개인정보 처리방침
<ChevronForward width={20} height={20} fill="#000000" />
</button>
🤖 Prompt for AI Agents
In src/components/settingTab/InfoSetting.tsx around lines 131 to 141, the code
uses an anchor with href="#" that prevents default and shows an alert; replace
that anchor with a semantic <button type="button"> element (keeping the same
className and onClick handler) to avoid scroll/address changes and improve
semantics and accessibility, ensure the onClick shows the same alert, add
accessible attributes as needed (e.g., aria-label or aria-disabled if required)
and keep the ChevronForward icon rendering unchanged.

Comment on lines +20 to 25
const { useGetMemberInfo } = useAccount();
const { data: memberInfo } = useGetMemberInfo();

if (error) return <Navigate to="/error" replace />;
if (isLoading) {
return (
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

두 쿼리의 로딩/에러 상태를 통합 처리하고 username 파생값을 생성하세요

현재는 등급 쿼리의 로딩/에러만 처리합니다. 회원정보 쿼리 실패 시도 동일하게 에러 페이지로 이동하거나 적절한 폴백을 주는 편이 안전합니다. 또한 username은 안전한 파생값으로 만들어 JSX에서 간결하게 사용하세요.

-    const { useGetMemberInfo } = useAccount();
-    const { data: memberInfo } = useGetMemberInfo();
+    const { useGetMemberInfo } = useAccount();
+    const { data: memberInfo, isLoading: isMemberLoading, error: memberError } = useGetMemberInfo();
+    const username = memberInfo?.result?.username ?? gradeData?.result?.username ?? '회원님';
 
-    if (error) return <Navigate to="/error" replace />;
-    if (isLoading) {
+    if (error || memberError) return <Navigate to="/error" replace />;
+    if (isLoading || isMemberLoading) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { useGetMemberInfo } = useAccount();
const { data: memberInfo } = useGetMemberInfo();
if (error) return <Navigate to="/error" replace />;
if (isLoading) {
return (
const { useGetMemberInfo } = useAccount();
const { data: memberInfo, isLoading: isMemberLoading, error: memberError } = useGetMemberInfo();
const username = memberInfo?.result?.username ?? gradeData?.result?.username ?? '회원님';
if (error || memberError) return <Navigate to="/error" replace />;
if (isLoading || isMemberLoading) {
return (
🤖 Prompt for AI Agents
In src/pages/home/HomePage.tsx around lines 20 to 25, consolidate loading/error
handling for both queries (the existing grade query and the memberInfo query) so
that if either query has error you Navigate to "/error" and if either is loading
you show the existing loading state; also create a safe derived username
constant (e.g. compute username from memberInfo fields with nullish fallback or
empty string) and use that variable in the JSX instead of accessing memberInfo
inline.

<div className="flex flex-col">
<div className="flex items-center gap-2">
<span className="font-heading2">{gradeData?.result.username}</span>
<span className="font-heading2">{memberInfo?.result?.username}</span>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

memberInfo 미도착 시 빈 텍스트 방지 및 가독성 향상

파생 변수로 만든 username을 사용해 폴백을 일관되게 적용하세요.

-                        <span className="font-heading2">{memberInfo?.result?.username}</span>
+                        <span className="font-heading2">{username}</span>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<span className="font-heading2">{memberInfo?.result?.username}</span>
<span className="font-heading2">{username}</span>
🤖 Prompt for AI Agents
In src/pages/home/HomePage.tsx around line 38, the JSX currently uses
memberInfo?.result?.username directly which can render empty text when
memberInfo is not available; derive a username variable earlier (e.g. const
username = memberInfo?.result?.username ?? '' or another appropriate fallback)
and replace the direct access with that derived username so the fallback is
applied consistently and prevents empty text while improving readability.

@Seojegyeong Seojegyeong merged commit e606f9a into develop Aug 14, 2025
3 checks passed
@Seojegyeong Seojegyeong deleted the feature/#90 branch August 14, 2025 07:26
@yeonjin719 yeonjin719 changed the title feat: 설정API 연동 완성 [Feature] 설정API 연동 Aug 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ [Feature] 설정 API 연결

4 participants